/*

    dsh-disclosure-triangle  Disclosure triangle container.
    Copyright (c) 2007-2009 held jointly by the individual authors.

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published
    by the Free Software Foundation; either version 3 of the License, or (at
    your option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
    License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this library;  if not, write to the Free Software Foundation,
    Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.

    > http://www.fsf.org/licensing/licenses/lgpl.html
    > http://www.opensource.org/licenses/lgpl-license.php

 */
package util.ui;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.RootPaneContainer;
import javax.swing.SwingConstants;
import javax.swing.UIManager;

/**
 * Disclosure triangle container.
 * http://www.dishevelled.org/disclosure-triangle/
 * 
 * @author Michael Heuer
 * @version $Revision$ $Date$
 */
public final class DisclosureTriangle extends JPanel {
  /** Label. */
  private final JLabel label;

  /** Wrapped container. */
  private final Container container;

  /** True if this disclosure triangle container is collapsed. */
  private boolean collapsed;

  /** Collapse icon. */
  private Icon collapseIcon;

  /** Expand icon. */
  private Icon expandIcon;

  /** Default label text, <code>"Details"</code>. */
  public static final String DEFAULT_LABEL_TEXT = "Details";

  /**
   * Create a new disclosure triangle container wrapping the specified
   * container.
   * 
   * <p>
   * By default this disclosure triangle container will be collapsed, its label
   * text will be <code>"Details"</code>, its collapse icon will be the icon
   * resource returned by <code>UIManager.getIcon("Tree.expandedIcon")</code>,
   * and its expand icon will be the icon resource returned by
   * <code>UIManager.getIcon("Tree.collapsedIcon")</code>.
   * </p>
   * 
   * @see #DEFAULT_LABEL_TEXT
   * @see javax.swing.UIManager#getIcon
   * @param container
   *          container to wrap, must not be null
   */
  public DisclosureTriangle(final Container container) {
    super();
    if (container == null) {
      throw new IllegalArgumentException("container must not be null");
    }
    this.container = container;

    collapsed = true;
    collapseIcon = UIManager.getIcon("Tree.expandedIcon");
    expandIcon = UIManager.getIcon("Tree.collapsedIcon");
    label = new JLabel(DEFAULT_LABEL_TEXT, expandIcon, SwingConstants.LEADING);

    label.addMouseListener(new MouseAdapter() {
      /** {@inheritDoc} */
      public void mouseClicked(final MouseEvent event) {
        if (collapsed) {
          expand();
        } else {
          collapse();
        }
      }
    });

    setLayout(new BorderLayout());
    add("North", label);
  }

  /**
   * Expand this disclosure triangle container.
   */
  public void expand() {
    setCollapsed(false);
  }

  /**
   * Actually perform the expand operation.
   */
  private void doExpand() {
    add("Center", container);
    label.setIcon(collapseIcon);
    Container rootPaneContainer = getParentRootPaneContainer();
    if (rootPaneContainer != null) {
      Dimension d0 = rootPaneContainer.getSize();
      Dimension d1 = container.getSize();
      Dimension d2 = container.getPreferredSize();
      rootPaneContainer.setSize(d0.width, d0.height + Math.max(d1.height, d2.height));
    }
  }

  /**
   * Collapse this disclosure triangle container.
   */
  public void collapse() {
    setCollapsed(true);
  }

  /**
   * Actually perform the collapse operation.
   */
  private void doCollapse() {
    remove(container);
    label.setIcon(expandIcon);
    Container rootPaneContainer = getParentRootPaneContainer();
    if (rootPaneContainer != null) {
      Dimension d0 = rootPaneContainer.getSize();
      Dimension d1 = container.getSize();
      Dimension d2 = container.getPreferredSize();
      rootPaneContainer.setSize(d0.width, d0.height - Math.max(d1.height, d2.height));
    }
  }

  /**
   * Return true if this disclosure triangle container is collapsed.
   * 
   * @return true if this disclosure triangle container is collapsed
   */
  public boolean isCollapsed() {
    return collapsed;
  }

  /**
   * Set to true to collapse this disclosure triangle container. Alternatively,
   * call <code>collapse()</code> or <code>expand()</code> as appropriate.
   * 
   * <p>
   * This is a bound property.
   * </p>
   * 
   * @param collapsed
   *          true to collapse this disclosure triangle container
   */
  public void setCollapsed(final boolean collapsed) {
    boolean oldCollapsed = this.collapsed;
    if (collapsed && !oldCollapsed) {
      doCollapse();
    }
    if (!collapsed && oldCollapsed) {
      doExpand();
    }
    this.collapsed = collapsed;
    firePropertyChange("collapsed", oldCollapsed, this.collapsed);
  }

  /**
   * Return the parent root pane container for this disclosure triangle
   * container or null if one does not exist.
   * 
   * @return the parent root pane container for this disclosure triangle
   *         container or null if one does not exist
   */
  private Container getParentRootPaneContainer() {
    Container c = this;
    while (!(c instanceof RootPaneContainer)) {
      if (c.getParent() == null) {
        return null;
      }
      c = c.getParent();
    }
    return c;
  }

  /**
   * Return the label text for this disclosure triangle container.
   * 
   * @return the label text for this disclosure triangle container
   */
  public String getLabelText() {
    return label.getText();
  }

  /**
   * Set the label text for this disclosure triangle container to
   * <code>labelText</code>.
   * 
   * <p>
   * This is a bound property.
   * </p>
   * 
   * @param labelText
   *          label text for this disclosure triangle container
   */
  public void setLabelText(final String labelText) {
    String oldLabelText = label.getText();
    label.setText(labelText);
    firePropertyChange("labelText", oldLabelText, label.getText());
  }

  /**
   * Return the collapse icon for this disclosure triangle container.
   * 
   * @return the collapse icon for this disclosure triangle container
   */
  public Icon getCollapseIcon() {
    return collapseIcon;
  }

  /**
   * Set the collapse icon for this disclosure triangle container to
   * <code>collapseIcon</code>.
   * 
   * <p>
   * This is a bound property.
   * </p>
   * 
   * @param collapseIcon
   *          collapse icon for this disclosure triangle container
   */
  public void setCollapseIcon(final Icon collapseIcon) {
    Icon oldCollapseIcon = this.collapseIcon;
    this.collapseIcon = collapseIcon;
    firePropertyChange("collapseIcon", oldCollapseIcon, this.collapseIcon);
    if (!collapsed) {
      label.setIcon(this.collapseIcon);
    }
  }

  /**
   * Return the expand icon for this disclosure triangle container.
   * 
   * @return the expand icon for this disclosure triangle container
   */
  public Icon getExpandIcon() {
    return expandIcon;
  }

  /**
   * Set the expand icon for this disclosure triangle container to
   * <code>expandIcon</code>.
   * 
   * <p>
   * This is a bound property.
   * </p>
   * 
   * @param expandIcon
   *          expand icon for this disclosure triangle container
   */
  public void setExpandIcon(final Icon expandIcon) {
    Icon oldExpandIcon = this.expandIcon;
    this.expandIcon = expandIcon;
    firePropertyChange("expandIcon", oldExpandIcon, this.expandIcon);
    if (collapsed) {
      label.setIcon(this.expandIcon);
    }
  }
}