/*
    VLDocking Framework 2.1
    Copyright VLSOLUTIONS, 2004-2006

    email : info@vlsolutions.com
------------------------------------------------------------------------
This software is distributed under the CeCILL license, a GNU GPL-compatible
license adapted to french law.
French and English license headers are provided at the begining of
the source files of this software application.
------------------------------------------------------------------------
LICENCE CeCILL (FRENCH VERSION).
------------------------------------------------------------------------
Ce logiciel est un programme informatique servant à améliorer les interfaces
homme-machine d'applications Java basées sur Swing, en leur apportant un
ensemble de fonctions relatives au dockage des composants.

Ce logiciel est régi par la licence CeCILL soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme,  le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard  l'attention de l'utilisateur est attirée sur les risques
associés au chargement,  à l'utilisation,  à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant  des  connaissances  informatiques approfondies.  Les
utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.

------------------------------------------------------------------------
CeCILL License (ENGLISH VERSION)
------------------------------------------------------------------------

This software is a computer program whose purpose is to enhance Human-Computer
Interfaces written in Java with the Swing framework, providing them a set of
functions related to component docking.

This software is governed by the CeCILL  license under French law and
abiding by the rules of distribution of free software.  You can  use,
modify and/ or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability.

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and,  more generally, to use and operate it in the
same conditions as regards security.

The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.

*/


package com.vlsolutions.swing.tabbedpane;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;

/** An Icon allowing the use of closeable tabs by simulating the entire tabpane tab-painting.
 *
 *<p>
 * As JTabbedPanes cannot use any JComponents as tab selectors (the access if protected and we just have
 * a label, an icon and a tooltip), we have to rely on tricks to bypass them.
 *
 *
 *
 * @author Lilian Chamontin, VLSolutions
 * @update 2005/11/01 Lilian Chamontin : fixed NPE when otherIcons == null (tabs without smart icons)
 * @update 2005/11/08 Lilian Chamontin : fixed bug related to event management when multiple other-icons are used
 * (big thanks to Emmanuel GAUVRIT).
 * @update 2005/11/21 Lilian Chamontin : enhanced width calculation of the icon size.
 */
public class JTabbedPaneSmartIcon implements Icon, Cloneable {
  /* Implementation : This icon is larger than standard icons : it also paints the tab title and optional buttons.
   * Inner Event management (with JTabbedPaneSmartIconManager) allows simulation of action on the buttons.
   *
   */
  
  /** The traditional tab selector icon */
  private Icon icon;
  
  /** calculated used width (icon + label + other icons) + gaps */
  private int width;
  
  /** x location where the other icons are drawn*/
  private int otherIconsOffset;
  
  /** tab height */
  private int height;
  
  /** the traditional tab selector label */
  private String label;
  
  /** the traditional tab selector tooltip text*/
  private String tooltipText;
  
  /** local tooltip text : the one to use during mouse movements (depends on the mouse position,
   * and can be either the tab selector tooltip text or one of the smart buttons included into this icon).
   */
  private String localTooltipText;
  
  // list of additional icons (presented as buttons)
  private SmartIconJButton [] otherIcons;
  
  /** currently pressed inner button */
  private SmartIconJButton pressedButton;
  
  /** currently rolled-over inner button */
  private SmartIconJButton rolloverButton;
  
  
  /** gap between the icon and the text*/
  private int textIconGap;
  
  /** gap between the text and the following icons*/
  private int otherIconsGap;
  
  private boolean antialiased = false;
  
  /* The container this icon is for (required to calculate proper widths and heights) */
  private JTabbedPane container;
  
  private int inBetweenOtherIconsGap;
  
  /** Constructs a new smart icon with a given set of additional buttons */
  public JTabbedPaneSmartIcon(Icon icon, String label, SmartIconJButton [] otherIcons) {
    this.icon = icon;
    this.label = label;
    this.otherIcons = otherIcons;
    
    this.textIconGap = UIManager.getInt("TabbedPane.textIconGap");
    this.otherIconsGap = UIManager.getInt("TabbedPane.otherIconsGap");
    this.inBetweenOtherIconsGap = UIManager.getInt("TabbedPane.inBetweenOtherIconsGap");
    invalidateSize();
    try {
      //mimic the unofficial aa settings of Swing
      this.antialiased = "true".equals(System.getProperty("swing.aatext"));
    } catch (SecurityException ignore){ // for untrusted web start apps failing gracefully
    }
    
  }
  
  public SmartIconJButton getSmartButton(int index){
    return otherIcons[index];
  }
  
  public void setSmartButton(int index, SmartIconJButton btn){
    otherIcons[index] = btn;
    invalidateSize();
  }
  
  
  /** Creates a shalow copy of this icon */
  public JTabbedPaneSmartIcon copy(){
    return (JTabbedPaneSmartIcon) clone();
  }
  
  /** {@inheritDoc} */
  public Object clone(){
    try {
      return super.clone();
    } catch (CloneNotSupportedException ignore){
      ignore.printStackTrace();
      return null;
    }
  }
  
  /** Update the tooltip of this icon */
  public void setTooltipText(String tooltip){
    this.tooltipText = tooltip;
  }
  
  /** Return the tooltip of this icon */
  public String getTooltipText(){
    return this.tooltipText;
  }
  
  /** Return the local tooltip of this icon (the one associated with inner mouse movements)*/
  public String getLocalTooltipText(){
    return this.localTooltipText;
  }
  
  /** Update the label to be displayed on the tab */
  public void setLabel(String label){
    this.label = label;
    invalidateSize();
  }
  
  /** Return the tab label */
  public String getLabel(){
    return this.label;
  }
  
  /** Update the main icon (left) to be displayed on the tab */
  public void setIcon(Icon icon){
    this.icon = icon;
    invalidateSize();
  }
  
  /** Return the tab icon */
  public Icon getIcon(){
    return this.icon;
  }
  
  private void invalidateSize(){
    this.width = this.height = -1;
  }
  
  /** Specify which container will use this icon.
   * <p> If the icon is shared between containers, please provide at least one as
   * this allows the icon to properly estimate its dimension.
   */
  public void setIconForTabbedPane(JTabbedPane container){
    this.container = container;
  }
  
  
  /** paints the icon (and the associated label and sub-icons) */
  public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g;
    Object renderingValue = null;
    if (this.antialiased){
      renderingValue = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    }
    if (icon != null){
      icon.paintIcon(c, g, x, y);
    }
    
    Font f = UIManager.getFont("JTabbedPaneSmartIcon.font"); // 2006/01/23
    if (f != null){
      g.setFont(f);
    }
    
    // reevaluate the width with correct graphics object, in case something has changed
    FontMetrics fm = g.getFontMetrics();
    int iconsOffset = otherIconsOffset;
    
    if (icon != null){
      g.drawString(label, x+icon.getIconWidth() + textIconGap, y + height - fm.getDescent());
    } else {
      g.drawString(label, x, y + height - fm.getDescent());
    }
    if (otherIcons != null){    //2005/11/01
      for (int i=0; i < otherIcons.length; i++){
        otherIcons[i].paintIcon(c, g, x + iconsOffset, y + height / 2 - otherIcons[i].getIconHeight()/2);
        iconsOffset += otherIcons[i].getIconWidth();
        if (i < otherIcons.length-1){
          iconsOffset+= inBetweenOtherIconsGap;
        }
      }
    }
    
    if (antialiased){
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingValue);
    }
    
  }
  
  
  private SmartIconJButton findButtonAt(Point p){
    int start = otherIconsOffset;
    if (otherIcons != null){    //2005/11/01
      for (int i=0; i < otherIcons.length; i++){
        SmartIconJButton btn = otherIcons[i];
        if (p.x >= start && p.x < start + btn.getIconWidth()){
          if (p.y >= height/2 - btn.getIconHeight() /2
              && p.y < height/2 + btn.getIconHeight() /2){
            return btn;
          } else {
            return null;
          }
        }
        start += btn.getIconWidth(); // bug corrected thanks to Emmanuel GAUVRIT 2005/11/08
        if (i < otherIcons.length-1){
          start += inBetweenOtherIconsGap;
        }
      }
    }
    return null;
  }
  
  /** Returns the width of this icon */
  public int getIconWidth() {
    if (width == -1){
      if (container == null){
        throw new NullPointerException("container for this smart icon not specified with setIconForTabbedPane()");
      }
      // reevaluate the width with correct graphics object, in case something has changed
      //Font f = UIManager.getFont("DockTabbedPane.font"); // 2006/01/23
      Font f = UIManager.getFont("JTabbedPaneSmartIcon.font"); // 2006/01/23
      FontMetrics fm = container.getFontMetrics(f);
      this.width = 0;
      if (icon != null){
        width = icon.getIconWidth();
      }
      width += textIconGap +  fm.stringWidth(label) + otherIconsGap;
      
      this.otherIconsOffset = width;
      int iconsOffset = otherIconsOffset;
      if (otherIcons != null){
        for (int i=0; i < otherIcons.length; i++){
          width += otherIcons[i].getIconWidth(); // additional width for icons
          if (i < otherIcons.length-1){
            width += inBetweenOtherIconsGap;
          }
        }
      }
    }
    return width;
    
  }
  
  /** Returns the height of this icon */
  public int getIconHeight() {
    if (height == -1){
      if (icon != null){
        height = icon.getIconHeight();
      } else {
        height = 16; // standard height (as if there was an icon) : should be calculated instead of fixed
      }
    }
    return height;
  }
  
  /** Process the mouse pressed event.
   *<p>
   * Mouse coordinates are given relative to this icon
   */
  public boolean onMousePressed(MouseEvent e){
    // find the icon under the point
    SmartIconJButton btn = findButtonAt(e.getPoint());
    if (btn != null && btn.isEnabled()){
      btn.setPressed(true);
      pressedButton = btn;
      return true;
    }
    return false;
  }
  
  /** Process the mouse released event.
   *<p>
   * Mouse coordinates are given relative to this icon
   */
  public boolean onMouseReleased(final MouseEvent e){
    if (pressedButton != null && pressedButton.isEnabled()){
      pressedButton.setPressed(false);
      final SmartIconJButton btn = findButtonAt(e.getPoint());
      if (btn == pressedButton){
        btn.fireAction(new ActionEvent(e.getSource(),
            ActionEvent.ACTION_PERFORMED, "" ));
        return true;
      }
    }
    return false;
  }
  
  /** Process the mouse exited event.
   *<p>
   * Mouse coordinates are given relative to this icon
   */
  public boolean onMouseExited(MouseEvent e){
    // reset all rollover states
    if (rolloverButton != null){
      rolloverButton.setRollover(false);
      rolloverButton = null;
      localTooltipText = tooltipText;
      return true;
    }
    rolloverButton = null;
    return false;
  }
  
  
  /** Process the mouse moved event.
   *<p>
   * Mouse coordinates are given relative to this icon
   */
  public boolean onMouseMoved(MouseEvent e){
    // check for a rollover effect
    SmartIconJButton btn = findButtonAt(e.getPoint());
    boolean shouldRepaint = false;
    if (btn != null){
      String tip = btn.getTooltipText();
      if (tip != null){
        if (!tip.equals(localTooltipText)){
          this.localTooltipText = tip;
          shouldRepaint = true;
        }
      } else if (localTooltipText != tooltipText){
        this.localTooltipText = tooltipText;
        shouldRepaint = true;
      }
      if (btn == rolloverButton){
        // still on the same button
      } else {
        // another button
        if (rolloverButton != null){
          rolloverButton.setRollover(false);
        }
        rolloverButton = btn;
        rolloverButton.setRollover(true);
        shouldRepaint = true;
      }
    } else if (rolloverButton != null){
      rolloverButton.setRollover(false);
      rolloverButton = null;
      localTooltipText = tooltipText;
      shouldRepaint = true;
    }
    
    return shouldRepaint;
  }
  
}
