package org.openswing.swing.mdi.client;

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

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;

import org.openswing.swing.client.*;
import org.openswing.swing.form.client.*;
import org.openswing.swing.logger.client.*;
import org.openswing.swing.tree.client.*;
import org.openswing.swing.util.client.*;
import org.openswing.swing.util.java.*;


/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: Base Internal Frame: to use together with MDI Frame.
 * You can set this internal frame as modal, by means of setModal() method BUT ONLY AFTER invoking MDIFrame.add() method,
 * i.e. after internal frame is already visible.</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 InternalFrame extends JInternalFrame {

  /** children windows (optional); they will be automatically closed when this window is closed */
  private ArrayList frameList = new ArrayList();

  /** parent frame (optional) */
  private InternalFrame  parentFrame;

  /** used when this window will be closed: if this flag is set to <code>true</code> then a warning dialog will be  showed before close the window to ask if it must be close; default value: ClientSettings.ASK_BEFORE_CLOSE */
  private boolean askBeforeClose = ClientSettings.ASK_BEFORE_CLOSE;

  /** last border of internal frame, before hiding title bar */
  private Border lastBorder = null;

  /** flag used to hide/show title bar; default value: <code>false</code> i.e. title bar is visible */
  private boolean hideTitleBar = false;

  /** last component added to the north of the internal frame, before hiding title bar */
  private JComponent lastNorthPane = null;

  /** internal frame modal state; default value: <code>false</code> i.e. internal frame is not modal */
  private boolean modal;

  /** internal frame owner */
  protected javax.swing.JInternalFrame owner = this;

  /** used to define whether this window can be opened more times or only one instance can be created per time; default value: <code>false</code>, i.e. any number of instances of this window can be created */
  private boolean uniqueInstance = false;


  /**
   * Costructor.
   */
  public InternalFrame() {
    super("",true,true,true,true);
    setFrameIcon(new ImageIcon(ClientUtils.getImage(ClientSettings.ICON_FILENAME)));
    this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    this.addInternalFrameListener(new InternalFrameAdapter() {

      public void internalFrameClosing(InternalFrameEvent e) {
        try {
          closeFrame();
        }
        catch (PropertyVetoException ex) {
        }
      }

      /**
       * Invoked when an internal frame is activated.
       */
      public void internalFrameActivated(InternalFrameEvent e) {
      }

      /**
       * Invoked when an internal frame is de-activated.
       */
      public void internalFrameDeactivated(InternalFrameEvent e) {
        if (ClientSettings.MDI_TOOLBAR!=null)
          ClientSettings.MDI_TOOLBAR.disableAllButtons();
      }


    });
  }


  /**
   * Create a link between this window and the parent window.
   * @param parent parent window
   */
  public final void setParentFrame(InternalFrame parentFrame) {
    this.parentFrame = parentFrame;
  }


  /**
   * @return list of TreePanel/GridControl/Form objects within this that is in insert/edit mode and that contain changes, an empty list otherwise
   */
  public final ArrayList checkComponents() {
    return checkComponents(this.getComponents());
  }


  /**
   * @param c components to analize
   * @return list of TreePanel/GridControl/Form objects within c components, that are in insert/edit mode and that contain changes, otherwise an empty list
   */
  private ArrayList checkComponents(Component[] c) {
    ArrayList aux = new ArrayList();
    for(int i=0;i<c.length;i++) {
      if (c[i] instanceof GridControl) {
        c[i].transferFocus();
        if (((GridControl)c[i]).getMode()==Consts.INSERT)
          aux.add(c[i]);
        if (((GridControl)c[i]).getMode()==Consts.EDIT &&
          ((GridControl)c[i]).getVOListTableModel().getChangedRowNumbers().length>0) {
          aux.add(c[i]);
        }
      }
      else if (c[i] instanceof Form) {
        try {
          Component obj = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
          if (obj!=null) {
            FocusListener[] f = obj.getFocusListeners();
            if (f!=null)
              for(int j=0;j<f.length;j++)
                f[j].focusLost(new FocusEvent(this,FocusEvent.FOCUS_LOST));
          }
            obj.transferFocus();
        }
        catch (Exception ex) {
          ((Form) c[i]).transferFocus();
        }
        boolean changed = ((Form)c[i]).isChanged();
        if (((Form)c[i]).getMode()==Consts.INSERT && changed)
          aux.add(c[i]);
        if (((Form)c[i]).getMode()==Consts.EDIT && changed)
          aux.add(c[i]);
      }
      else if (c[i] instanceof TreePanel) {
        if (((TreePanel)c[i]).isChanged())
          aux.add(c[i]);
      }
      else if (c[i] instanceof JScrollPane) {
        aux.addAll(checkComponents( ((JScrollPane)c[i]).getViewport().getComponents() ));
      }
      else if (c[i] instanceof Container) {
        aux.addAll(checkComponents( ((Container)c[i]).getComponents() ));
      }
    }
    return aux;
  }


  /**
   * Callback method invoked by "closeFrame" when internal frame contains changes and the user selects "yes" option on dialog about saving changes:
   * If this method is not ovverrided, then "closeFrame" will attempt to automatically save each changed objects (Form, Grid, TreePanel)
   * and this automation could be incorrect, in case of multiple objects to save in a specific order,
   * if this is the case, then "saveChanges" method should be overrided, in order to specify a custom saving strategy.
   * @return <code>true</code> if changes has been correctly saved, <code>false</code> otherwise, i.e. some error occours on saving data, so frame must NOT be closed; as default dehavior it throws an exception: UnsupportedOperationException, i.e. closeFrame method automatically attempts to save changes
   */
  public boolean saveChanges() {
    throw new UnsupportedOperationException("No check performed");
  }


  /**
   * Supports reporting constrained property changes.
   * This method can be called when a constrained property has changed
   * and it will send the appropriate <code>PropertyChangeEvent</code>
   * to any registered <code>VetoableChangeListeners</code>.
   *
   * @param propertyName  the name of the property that was listened on
   * @param oldValue  the old value of the property
   * @param newValue  the new value of the property
   * @exception PropertyVetoException when the attempt to set the
   *		property is vetoed by the component
   */
  protected final void fireVetoableChange(String propertyName, Object oldValue, Object newValue)
      throws java.beans.PropertyVetoException
  {
    if (IS_CLOSED_PROPERTY.equals(propertyName) && Boolean.FALSE.equals(oldValue) && Boolean.TRUE.equals(newValue)) {
      throw new PropertyVetoException("",new PropertyChangeEvent(this,IS_CLOSED_PROPERTY,Boolean.TRUE,Boolean.FALSE));
    }
    else
      super.fireVetoableChange(propertyName, oldValue, newValue);
  }



  /**
   * This method is called when this window will be closed.
   * The first method calle is beforeCloseFrame; if this will return <code>false</code> then the closing window operation will be interrupted.
   */
  public final void closeFrame() throws PropertyVetoException {
    if (!beforeCloseFrame()) {
      return;
    }
    if (askBeforeClose) {
      // check if this contains a GridControl or a Form panel in insert/edit mode and they contain changes...
      ArrayList changedObjects = checkComponents();
      if (changedObjects.size()>0) {
        int res = OptionPane.showConfirmDialog(
          this,
          "save changes?",
          "confirm window closing",
          JOptionPane.YES_NO_CANCEL_OPTION
        );

        if (res==JOptionPane.YES_OPTION) {
          try {
            boolean ok = saveChanges();
            if (!ok) {
              // save task contains errors: frame closing will be aborted...
              return;
            }
          }
          catch (UnsupportedOperationException ex1) {
            // "saveChanged" method was not overrided:
            // each changed object will be saved automatically
            // NOTE: this automation could be incorrect, in case of multiple objects to save in a specific order,
            //       if this is the case, then "saveChanges" method should be overrided, in order to specify a custom saving strategy
            for(int i=0;i<changedObjects.size();i++) {
              if (changedObjects.get(i) instanceof GridControl) {
                if (!((GridControl)changedObjects.get(i)).save())
                  return;
              }
              else if (changedObjects.get(i) instanceof Form) {
                if (!((Form)changedObjects.get(i)).save()) {
                  return;
                }
              }
              else if (changedObjects.get(i) instanceof TreePanel) {
                Logger.error(this.getClass().getName(), "closeFrame", "Error while saving TreePanel: you have to override 'saveChanges' method.",null);
                return;
              }
            }
          }
        }
        if (res==JOptionPane.CANCEL_OPTION) {
          return;
        }
      }
    }
    try{
      // close all children windows...
      while(frameList.size()>0){
        InternalFrame frame = (InternalFrame)frameList.get(0);
        frameList.remove(0);
        frame.closeFrame();
      }
      // remove link with the parent window...
      if (parentFrame!=null)
        parentFrame.popFrame(this);
    } catch (Exception e){
      e.printStackTrace();
    }
    try {
      ClientUtils.disposeComponents(getContentPane().getComponents());

      setVisible(false);
      dispose();
      MDIFrame.getWindowMenu().removeWindow(this);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }


  /**
   * Remove a child window from this.
   * It's called by closeFrame.
   * @param frame child window to remove
   */
  public final InternalFrame popFrame(InternalFrame frame) {
    if (frame!=null)
      frameList.remove(frame);
    return frame;
  }


  /**
   * Add a child window to this.
   * @param frame child window to add
   */
  public final void pushFrame(InternalFrame frame) {
    frameList.add(frame);
  }


  /**
   * Callback method invoked by closeFrame.
   * @return <code>true</code> allows the closing operation to continue, <code>false</code> the closing operation will be interrupted
   */
  protected boolean beforeCloseFrame() {
    return true;
  }


  /**
   * @return used when this window will be closed: if this flag is set to <code>true</code> then a warning dialog will be  showed before close the window to ask if it must be close
   */
  public final boolean isAskBeforeClose() {
    return askBeforeClose;
  }


  /**
   * Used when this window will be closed: if this flag is set to <code>true</code> then a warning dialog will be  showed before close the window to ask if it must be close
   * @param askBeforeClose <code>true</code> to show a warning dialog before close the window to ask if it must be close
   */
  public final void setAskBeforeClose(boolean askBeforeClose) {
    this.askBeforeClose = askBeforeClose;
  }


  /**
   * Define if title bar must be hidden or showed
   * @param hideTitleBar flag used to hide/show title bar; <code>true</code> to hide title bar; <code>false</code> to show it
   */
  public final boolean isHideTitleBar() {
    return hideTitleBar;
  }



  /**
   * Define if title bar must be hidden or showed
   * @param hideTitleBar flag used to hide/show title bar; <code>true</code> to hide title bar; <code>false</code> to show it
   */
  public final void setHideTitleBar(boolean hideTitleBar) {
    this.hideTitleBar = hideTitleBar;
    if (hideTitleBar && !Beans.isDesignTime()) {
      super.setIconifiable(false);
      super.setMaximizable(false);
      lastNorthPane = ((javax.swing.plaf.basic.BasicInternalFrameUI)this.getUI()).getNorthPane();
      ((javax.swing.plaf.basic.BasicInternalFrameUI)this.getUI()).setNorthPane(null);
      lastBorder = super.getBorder();
      super.setBorder(BorderFactory.createEmptyBorder());
    }
    else if (lastNorthPane!=null && lastBorder!=null) {
      ((javax.swing.plaf.basic.BasicInternalFrameUI)this.getUI()).setNorthPane(lastNorthPane);
      super.setBorder(lastBorder);
    }
  }


  /**
   * @return internal frame owner; used in case of modal internal frame
   */
  public final javax.swing.JInternalFrame getOwner() {
    return owner;
  }


  /**
   * Set the internal frame owner; used in case of modal internal frame.
   * @param owner internal frame owner
   */
  public final void setOwner(javax.swing.JInternalFrame owner) {
     this.owner=owner;
  }


  /**
   * Define this internal frame as modal.
   * IMPORTANT NOTE: do not call this method before showing this frame, but only AFTER MDIFrame.add() invokation.
   * @param modal <code>true</code> to set this internal frame as modal, <code>false</code> otherwise
   */
  public final void setModal(boolean modal) {
      this.modal=modal;
      javax.swing.JDesktopPane desktop=getDesktopPane();
      if (desktop instanceof JDesktopPane) {
        ((DesktopPane)desktop).setModal(this,modal);
      }
  }


  /**
   * @return <code>true</code> whether this internal frame as modal, <code>false</code> otherwise
   */
  public final boolean isModal() {
     return modal;
  }


  /**
   * Iconify this internal frame.
   * Please do not call this method for modal internal frames!
   * @param iconifiable <code>true</code> to iconify internal frame
   */
  public final void setIconifiable(boolean iconifiable) {
    if (modal && iconifiable)
      throw new IllegalArgumentException("InternalFrame class cannot be iconifiable while modal");
    super.setIconifiable(iconifiable);
  }


  /*
   * Creates a new EventDispatchThread to dispatch events from this.
   * This method returns when stopModal is invoked.
   */
  public synchronized void startModal() {
    if (isVisible() && !isShowing()) {
      Container parent = this.getParent();
      while (parent!=null) {
        if (!parent.isVisible())
          parent.setVisible(true);
        parent = parent.getParent();
      }
    }

    try {
      if (javax.swing.SwingUtilities.isEventDispatchThread()) {
        EventQueue theQueue = getToolkit().getSystemEventQueue();
        while (isVisible()) {
          // This is essentially the body of EventDispatchThread
          AWTEvent event = theQueue.getNextEvent();
          Object src = event.getSource();
          // can't call theQueue.dispatchEvent, so I pasted its body here
          if (event instanceof ActiveEvent) {
              ((ActiveEvent) event).dispatch();
          } else if (src instanceof Component) {
              ((Component) src).dispatchEvent(event);
          } else if (src instanceof MenuComponent) {
              ((MenuComponent) src).dispatchEvent(event);
          } else {
            Logger.error(this.getClass().getName(), "startModal", "Unable to dispatch event: "+event,null);
          }
        }
      } else {
        while (isVisible())
          wait();
      }
    } catch(Exception ex){
      ex.printStackTrace();
    }
  }


  /*
   * Stops the event dispatching loop created by a previous call to <code>startModal</code>.
   */
  public synchronized void stopModal() {
    notifyAll();
  }


  /**
   * @return used to define whether this window can be opened more times or only one instance can be created per time
   */
  public final boolean isUniqueInstance() {
    return uniqueInstance;
  }


  /**
   * Define whether this window can be opened more times or only one instance can be created per time.
   * Default value: <code>false</code>, i.e. any number of instances of this window can be created.
   * @param uniqueInstance <code>true</code> only one instance of this window will be opened per time, <code>false</code> otherwise
   */
  public final void setUniqueInstance(boolean uniqueInstance) {
    this.uniqueInstance = uniqueInstance;
  }


}
