package org.openswing.swing.client;

import java.beans.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;

import org.openswing.swing.form.model.client.*;
import org.openswing.swing.logger.client.*;
import org.openswing.swing.util.client.*;


/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: check-box whose text is translated according to internalization settings.
 * As default behavior, check-box supports two states: Boolean.TRUE (selected) and Boolean.FALSE (deselected).
 * Optionally (when "allowNullValue" property has been set to <code>true</code>), check-box supports three states: Boolean.TRUE (selected) and Boolean.FALSE (deselected) and null (not selected and gray).</p>
 * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
 *
 * <p> This file is part of OpenSwing Framework.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the (LGPL) Lesser General Public
 * License as published by the Free Software Foundation;
 *
 *                GNU LESSER GENERAL PUBLIC LICENSE
 *                 Version 2.1, February 1999
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *       The author may be contacted at:
 *           maurocarniel@tin.it</p>
 *
 * @author Mauro Carniel
 * @version 1.0
 */
public class CheckBoxControl extends JCheckBox implements InputControl {


  /** attribute name to which link the input control */
  private String attributeName = null;

  /** label used when showing error messages related to the input control (optional) */
  private LabelControl label = null;

  /** define if the input control is enabled on INSERT mode */
  private boolean enabledOnInsert = true;

  /** define if the input control is enabled on EDIT model */
  private boolean enabledOnEdit = true;

  /** define if the input control value is duplicated when user has clicked on COPY button */
  private boolean canCopy = false;

  /** old input control value */
  protected Object oldValue;

  /** value changed listener list */
  private ArrayList valueChangedListeners = new ArrayList();

  /** default background color of the binding component */
  private Color defaultBackgroundColor = null;

  /** define if the input control value is changed */
  private boolean changed = false;

  /** tooltip text */
  private String toolTipText = null;

  /** define if null value is alloed (i.e. distinct from Boolean.FALSE value); default value: <code>false</code> */
  private boolean allowNullValue = false;

  /** ButtonModel associated to the check-box when "allowNullValue" property is set to <code>true</code> */
  private CheckBoxButtonModel model = null;


  public CheckBoxControl() {
    setOpaque(false);
    initListeners();
  }


  /**
   * @return current Font setting
   */
  public final Font getFont() {
    return super.getFont();
  }


  /**
   * Set the specified font.
   * @param font Font to set
   */
  public final void setFont(Font font) {
    super.setFont(font);
  }


  /**
   * Set a tooltip text. This text will be translated according to the internationalization settings.
   * @param toolTipText tool tip text entry in the dictionary
   */
  public final void setToolTipText(String toolTipText) {
    this.toolTipText = toolTipText;
    if (!Beans.isDesignTime())
      super.setToolTipText(ClientSettings.getInstance().getResources().getResource(toolTipText));
  }


  /**
   * @return tool tip text entry in the dictionary
   */
  public final String getToolTipText() {
    return toolTipText;
  }


  /**
   * Method called by the sub-class to initialize focus listener used to push input control value in the VOModel.
   * @param component component inside this
   */
  protected void initListeners() {
    defaultBackgroundColor = getBackground();

    addFocusListener(new FocusListener() {

      public void focusGained(FocusEvent e) {
        controlFocusGained(e);
      }

      public void focusLost(FocusEvent e) {
        controlFocusLost(e);
      }

    });

    addItemListener(new ItemListener() {
        public void itemStateChanged(ItemEvent e) {
          if (e.getStateChange()==e.SELECTED && attributeName!=null) {
            maybeFireValueChangedEvent();
          }
        }
    });

    ClientUtils.addTabListener(this);

  }


  /**
   * Add a value changed listener to this input control.
   * @param listener value changed listener to add
   */
  public final void addValueChangedListener(ValueChangeListener listener) {
    this.valueChangedListeners.add(listener);
  }


  /**
   * Remove a value changed listener from this input control.
   * @param listener value changed listener to remove
   */
  public final void removeValueChangedListener(ValueChangeListener listener) {
    this.valueChangedListeners.remove(listener);
  }


  /**
   * Method called by the sub-class to fire a value changed event.
   */
  protected void maybeFireValueChangedEvent() {
    // retrieve current value...
    Object newValue = getValue();

    if (isEnabled() &&
        ((this.oldValue==null && newValue!=null) ||
         (this.oldValue!=null && newValue==null) ||
         (this.oldValue!=null && newValue!=null && ! this.oldValue.equals(newValue)))) {

      // update old value...
      this.oldValue = newValue;

      // mark the input control as changed
      setChanged(true);

      // fire value changed events...
      ValueChangeEvent e = new ValueChangeEvent(this, attributeName, this.oldValue, newValue);
      for (int i = 0; i < this.valueChangedListeners.size(); i++)
        ((ValueChangeListener)this.valueChangedListeners.get(i)).valueChanged(e);
    }

  }


  /**
   * This method is called when the input control receives focus.
   */
  private void controlFocusGained(FocusEvent e) {
    oldValue = getValue();
    if (ClientSettings.VIEW_BACKGROUND_SEL_COLOR && isEnabled()) {
      UIManager.put("CheckBox.background", ClientSettings.BACKGROUND_SEL_COLOR);
      UIManager.put("CheckBoxMenuItem.selectionBackground", ClientSettings.BACKGROUND_SEL_COLOR);
      repaint();
    }
  }


  /**
   * This method is called when the input control losts focus.
   */
  private void controlFocusLost(FocusEvent e) {
    if (ClientSettings.VIEW_BACKGROUND_SEL_COLOR && isEnabled()) {
      UIManager.put("CheckBox.background", defaultBackgroundColor);
      UIManager.put("CheckBoxMenuItem.selectionBackground", defaultBackgroundColor);
      repaint();
    }
    maybeFireValueChangedEvent();
  }


  /**
   * Link the input control to the form which contains it and with the specified the attribute.
   * @param attributeName attribute name to which link the input control
   */
  public final void setAttributeName(String attributeName) {
    this.attributeName = attributeName;
  }


  /**
   * Link the input control to the form which contains it and with the specified the attribute.
   * @param attributeName attribute name to which link the input control
   */
  public final String getAttributeName() {
    return attributeName;
  }


  /**
   * @return mandatory property of the input control
   */
  public final boolean isRequired() {
    return false;
  }


  /**
   * Link the input control label to the specified label.
   * @param label label used when showing error messages related to the input control
   */
  public final void setLinkLabel(LabelControl label) {
    this.label = label;
  }


  /**
   * Link the input control to the form which contains it and with the specified the attribute.
   * @param attributeName attribute name to which link the input control
   */
  public final LabelControl getLinkLabel() {
    return label;
  }


  /**
   * @return <code>true</code> if the input control is enabled on INSERT mode, <code>false</code> otherwise
   */
  public final boolean isEnabledOnInsert() {
    return enabledOnInsert;
  }


  /**
   * Define if the input control is enabled on INSERT mode.
   * @param enabled <code>true</code> if the input control is enabled on INSERT mode, <code>false</code> otherwise
   */
  public final void setEnabledOnInsert(boolean enabled) {
    this.enabledOnInsert = enabled;
  }


  /**
   * @return <code>true</code> if the input control is enabled on EDIT mode, <code>false</code> otherwise
   */
  public final boolean isEnabledOnEdit() {
    return enabledOnEdit;
  }


  /**
   * Define if the input control is enabled on EDIT mode, <code>false</code> otherwise
   * @param enabled mandatory property of the input control
   */
  public final void setEnabledOnEdit(boolean enabled) {
    this.enabledOnEdit = enabled;
  }


  /**
   * @return define if the input control value is duplicated when user has clicked on COPY button
   */
  public final boolean isCanCopy() {
    return canCopy;
  }


  /**
   * Define if the input control value is duplicated when user has clicked on COPY button.
   * @param canCopy define if the input control value is duplicated when user has clicked on COPY button
   */
  public final void setCanCopy(boolean canCopy) {
    this.canCopy = canCopy;
  }



  /**
   * @return value related to the input control
   */
  public final Object getValue() {
    if (allowNullValue) {
      return model.getCurrentValue();
    }
    else
      return new Boolean(this.isSelected());
  }


  /**
   * Set value to the input control.
   * @param value value to set into the input control
   */
  public final void setValue(Object value) {
    if (allowNullValue) {
      try {
        model.setCurrentValue( (Boolean) value);
      }
      catch (ClassCastException ex) {
        Logger.error(this.getClass().getName(), "setValue", "Value is not of type Boolean: "+value.getClass().getName(),null);
      }
    }
    else {
      if (value==null)
        this.setSelected(false);
      else if (value instanceof Boolean)
        this.setSelected(((Boolean)value).booleanValue());
      else {
        Logger.error(this.getClass().getName(), "setValue", "Value is not of type Boolean: "+value.getClass().getName(),null);
      }
    }
  }


  /**
   * @return <code>true</code> if the input control value is changed, <code>false</code> otherwise
   */
  public final boolean isChanged() {
    return changed;
  }


  /**
   * Define if the input control value is changed.
   * @param changed <code>true</code> if the input control value is changed, <code>false</code> otherwise
   */
  public final void setChanged(boolean changed) {
    this.changed = changed;
  }


  /**
   * Set abilitation setting.
   * @param enabled flag used to set abilitation of control
   */
  public final void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    setFocusable(enabled || ClientSettings.DISABLED_INPUT_CONTROLS_FOCUSABLE);
    try {
      UIManager.put("CheckBox.background", (Color)UIManager.getColor("TextField.inactiveBackground"));
      UIManager.put("CheckBoxMenuItem.selectionBackground", (Color)UIManager.getColor("TextField.inactiveBackground"));
    }
    catch (Exception ex) {
    }
  }


  /**
   * Set check box label.
   * @param text check box label
   */
  public final void setText(String text) {
    if (Beans.isDesignTime())
      super.setText(text);
    else
      super.setText(ClientSettings.getInstance().getResources().getResource(text));
  }


  /**
   * @return define if null value is alloed (i.e. distinct from Boolean.FALSE value)
   */
  public final boolean isAllowNullValue() {
    return allowNullValue;
  }


  /**
   * Define if null value is alloed (i.e. distinct from Boolean.FALSE value)
   * @param allowNullValue define if null value is alloed (i.e. distinct from Boolean.FALSE value)
   */
  public final void setAllowNullValue(boolean allowNullValue) {
    this.allowNullValue = allowNullValue;

    if (allowNullValue) {
      MouseListener[] ll = this.getMouseListeners();
      for(int i=0;i<ll.length;i++)
        if (ll[i] instanceof BasicButtonListener)
          this.removeMouseListener(ll[i]);

      super.addMouseListener(new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            if (!CheckBoxControl.this.isEnabled())
                return;
            grabFocus();
            if (model.getCurrentValue()==null)
              model.setCurrentValue(Boolean.FALSE);
            else if (Boolean.TRUE.equals(model.getCurrentValue()))
              model.setCurrentValue(null);
            else if (Boolean.FALSE.equals(model.getCurrentValue()))
              model.setCurrentValue(Boolean.TRUE);
          }
      });
      ActionMap map = new ActionMapUIResource();
      map.put("pressed", new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
              grabFocus();
              if (model.getCurrentValue()==null)
                model.setCurrentValue(Boolean.FALSE);
              else if (Boolean.TRUE.equals(model.getCurrentValue()))
                model.setCurrentValue(null);
              else if (Boolean.FALSE.equals(model.getCurrentValue()))
                model.setCurrentValue(Boolean.TRUE);
          }
      });
      map.put("released", null);
      SwingUtilities.replaceUIActionMap(this, map);
      model = new CheckBoxButtonModel(getModel());
      setModel(model);
    }
  }


  public final void setSelected(boolean selected) {
    if (allowNullValue) {
      model.setCurrentValue(new Boolean(selected));
    }
    else
      super.setSelected(selected);
  }


  /**
   * @return value changed listeners list
   */
  public final ValueChangeListener[] getValueChangeListeners() {
    return (ValueChangeListener[])valueChangedListeners.toArray(new ValueChangeListener[valueChangedListeners.size()]);
  }


  /**
   * <p>Title: OpenSwing Framework</p>
   * <p>Description: Inner class used to support three states: Boolean.TRUE, Boolean.FALSE and null</p>
   * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
   * @author Mauro Carniel
   * @version 1.0
   */
  class CheckBoxButtonModel implements ButtonModel {

      private final ButtonModel model;


      public CheckBoxButtonModel(ButtonModel model) {
          this.model = model;
      }


      public void setCurrentValue(Boolean value) {
        if (value==null) {
          model.setArmed(true);
          setPressed(true);
          setSelected(false);
        } else if (Boolean.FALSE.equals(value)) {
          model.setArmed(false);
          setPressed(false);
          setSelected(false);
        }
        else if (Boolean.TRUE.equals(value)) {
          model.setArmed(false);
          setPressed(false);
          setSelected(true);
        }
      }


      public Boolean getCurrentValue() {
        if (isSelected()) {
          return Boolean.TRUE;
        }
        else if (!isSelected() && isArmed()) {
          return null;
        }
        else {
          return Boolean.FALSE;
        }
      }


      public final void setEnabled(boolean b) {
        setFocusable(b);
        model.setEnabled(b);
      }

      public boolean isSelected() {
        return model.isSelected();
      }

      public boolean isEnabled() {
        try {
          return model.isEnabled();
        }
        catch (Exception ex) {
          return false;
        }
      }

      public boolean isPressed() {
        return model.isPressed();
      }

      public boolean isRollover() {
        return model.isRollover();
      }

      public void setSelected(boolean selected) {
        model.setSelected(selected);
      }

      public void setPressed(boolean pressed) {
        model.setPressed(pressed);
      }

      public void setRollover(boolean rollover) {
        model.setRollover(rollover);
      }

      public void setMnemonic(int mnemonic) {
        model.setMnemonic(mnemonic);
      }

      public int getMnemonic() {
        return model.getMnemonic();
      }

      public void setActionCommand(String command) {
        model.setActionCommand(command);
      }

      public String getActionCommand() {
        return model.getActionCommand();
      }

      public void setGroup(ButtonGroup group) {
        model.setGroup(group);
      }

      public void addActionListener(ActionListener listener) {
        model.addActionListener(listener);
      }

      public void removeActionListener(ActionListener listener) {
        model.removeActionListener(listener);
      }

      public void addItemListener(ItemListener listener) {
        model.addItemListener(listener);
      }

      public void removeItemListener(ItemListener listener) {
        model.removeItemListener(listener);
      }

      public void addChangeListener(ChangeListener listener) {
        model.addChangeListener(listener);
      }

      public void removeChangeListener(ChangeListener listener) {
        model.removeChangeListener(listener);
      }

      public Object[] getSelectedObjects() {
        return model.getSelectedObjects();
      }

      public void setArmed(boolean armed) { }

      public boolean isArmed() {
        return model.isArmed();
      }

  }


}
