/*************************************************************************
 *
 *  $RCSfile: BasicOfficeBean.java,v $
 *
 *  $Revision: 1.4 $
 *
 *  last change: $Author: hr $ $Date: 2003/06/30 15:29:18 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  the BSD license.
 *  
 *  Copyright (c) 2003 by Sun Microsystems, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of Sun Microsystems, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *     
 *************************************************************************/

import java.awt.Component;
import java.awt.Container;
import java.awt.Canvas;
import java.awt.Window;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;

import com.sun.star.lang.XComponent;
import com.sun.star.lang.XEventListener;
import com.sun.star.lang.EventObject;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.DisposedException;
import com.sun.star.awt.XWindow;
import com.sun.star.awt.XWindowPeer;
import com.sun.star.awt.InvalidateStyle;
import com.sun.star.frame.XController;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XFrame;
import com.sun.star.frame.XFrames;
import com.sun.star.frame.XFramesSupplier;
import com.sun.star.frame.XModel;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XDispatchProvider;
import com.sun.star.frame.XSynchronousFrameLoader;
import com.sun.star.frame.XStorable;
import com.sun.star.frame.FrameActionEvent;
import com.sun.star.frame.FrameAction;
import com.sun.star.frame.FrameSearchFlag;
import com.sun.star.document.XTypeDetection;
import com.sun.star.document.MacroExecMode;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.PropertyVetoException;
import com.sun.star.beans.OfficeConnection;
import com.sun.star.beans.OfficeWindow;
import com.sun.star.beans.LocalOfficeWindow;
import com.sun.star.util.CloseVetoException;
import com.sun.star.util.XCloseable;
import com.sun.star.util.XModifiable;
import com.sun.star.util.XURLTransformer;
import com.sun.star.uno.UnoRuntime;

/**
 * This class provides a basic functionality of the office bean. 
 * The application developer can subclass from this class in order 
 * to provide application specific methods.
 */
public class BasicOfficeBean
	extends Container
{
	private transient OfficeConnection		mConnection;
	private transient OfficeWindow			mWindow;
	private transient CommandConveyor		mCommandConveyor;

	protected String				mDocumentURL;

	protected transient XMultiServiceFactory	mServiceFactory;
	protected transient Object			mDesktop;
	protected transient XFrame			mFrame;
	protected transient XModifiable			mModifiable;

	// slot command execution environment
	protected transient XDispatchProvider		mDispatcher;
	protected transient XURLTransformer		mURLTransformer;
	protected transient XTypeDetection		mTypeDetector;

	/**
	 * Constructor.
	 *
	 * This constructor sets default layout manager (BorderLayout) 
	 * and creates the command execution conveyor. It also sets the 
	 * default appearance of the bean.
	 */
	 public BasicOfficeBean()
	 {
	 	setLayout(new BorderLayout());
	 	mCommandConveyor	= new CommandConveyor();
	 	mCommandConveyor.start();
	 	add(new PlaceHolder());
	 }

	/**
	 * Sets the office connection object.
	 * A subclass can provide own implementation of this method 
	 * in order to set up the ContainerFactory on the connection 
	 * object, but it MUST chain with this method implementation.
	 *
	 * @param connection The connection object.
	 */
	public synchronized void setOfficeConnection(OfficeConnection connection)
		throws java.lang.IllegalStateException
	{
		if (mWindow != null)
			throw new java.lang.IllegalStateException(
				"The connection is still open.");
		mConnection	= connection;
		mConnection.addEventListener(new ConnectionListener());
	}

	/**
	 * Determines wether document was loaded or not.
	 *
	 * @return <code>true</code> if a document has been loaded.
	 */
	public boolean isDocumentLoaded()
	{
		return (mDocumentURL != null);
	}

	/**
	 * Loads a document referenced by a URL.
	 *
	 * @param url The document's URL string. 
	 * @exception java.io.IOException if the document loading process has 
	 *	failed.
	 */
	public synchronized void load(String url)
		throws java.io.IOException
	{
		try {
			if (mWindow == null)
				openConnection();

			// Determine the document URL.
			if (url.equals(""))
				url = getDefaultDocumentURL();

			// get XComponentLoader (<= 6.0 => global, >= 6.1 => from frame)
			XComponentLoader xLoader = (XComponentLoader) UnoRuntime.queryInterface(
					XComponentLoader.class, mFrame );
			if ( xLoader == null )
			{
				xLoader = (XComponentLoader)UnoRuntime.queryInterface( 
						XComponentLoader.class, mDesktop );
			}

			// Avoid Dialog 'Document changed' while reloading
			boolean bWasModified = isModified();
			XController xOldController = null;
			if ( mFrame != null && mFrame.getController() != null )
				xOldController = mFrame.getController();
			try {
				if ( mFrame != null && xOldController != null )
					xOldController.suspend(true);
				setModified(false);
			} catch (java.lang.IllegalStateException exp) {
			}

			// load the document.
			PropertyValue aArgs[] = new PropertyValue[1];
			aArgs[0] = new PropertyValue();
			aArgs[0].Name = "MacroExecutionMode";
			aArgs[0].Handle = -1;
			aArgs[0].Value = new Short( MacroExecMode.USE_CONFIG );
			XComponent xComponent = xLoader.loadComponentFromURL( url, 
				mFrame.getName(), FrameSearchFlag.ALL, aArgs );

			// nothing loaded?
			if ( xComponent == null )
			{
				// reactivate old document
				if ( mFrame != null && mFrame.getController() != null )
					mFrame.getController().suspend(false);
				setModified(true);

				// throw exception
				throw new java.io.IOException(
					"Can not load a document: \"" + url + "\"");
			}
			mDocumentURL = url;

			// Get document's XModifiable interface if any.
			mModifiable = (XModifiable)UnoRuntime.queryInterface(
				XModifiable.class, xComponent );

			// show window (just to test bean creation without system window)
			setVisible(true);
			((LocalOfficeWindow)mWindow).setVisible(true);

			// Find top most parent and force it to validate.
			Container	parent	= this;
			while(parent.getParent() != null)
				parent	= parent.getParent();
			((Window)parent).validate();

		} catch (com.sun.star.uno.Exception exp) {
			throw new java.io.IOException(exp.getMessage());
		}
	}

	/**
	 * Saves the document.
	 */
	public void save()
		throws java.io.IOException
	{
		if (isDocumentLoaded() == false)
			throw new java.io.IOException("The document has not been loaded.");
//		else if (mDocumentURL.startsWith("private:") == true)
//			throw new java.io.IOException("The document URL has not been defined.");
		XStorable	storable	= (XStorable)UnoRuntime.queryInterface(
			XStorable.class, mFrame.getController().getModel());
		try {
			storable.store();
		} catch (com.sun.star.io.IOException exp) {
			throw new java.io.IOException(exp.getMessage());
		}
	}

	/**
	 * Saves the document.
	 *
	 * @param url The location where the document should be stored.
	 */
	public void save(String url)
		throws java.io.IOException
	{
		// The implementation of this method MUST replace 
		// the document URL with new value on succesful save.

		throw new java.lang.UnsupportedOperationException(
			"There is no implementation for this operation.");
	}

	/**
	 * Queues an office command for asynchronous execution.
	 *
	 * @param command The office command for asynchronous execution.
	 */
	public void queue(OfficeCommand command)
	{
		if (command == null)
			throw new java.lang.IllegalArgumentException(
				"The command may not be null.");
		mCommandConveyor.queue(command);
	}

	/**
	 * Retrives a URL of the default office document.
	 *
	 * This method can be overriden by a subclass in order to customize 
	 * the default document URL.
	 *
	 * @return The URL of the default document.
	 */
	public String getDefaultDocumentURL()
	{
		return null;
	}

	/**
	 * Retrives an office document URL.
	 *
	 * @return The URL of the office document.
	 */
	public String getDocumentURL()
	{
		return mDocumentURL;
	}

	/**
	 * Determines is the office document modifiable.
	 *
	 * @return <code>true</code> if the document is modifiable.
	 */
	public boolean isModifiable()
	{
		return (mModifiable != null);
	}

	/**
	 * Sets the office document modification state.
	 *
	 * @param state The new state of document modification.
	 * @exception java.lang.IllegalStateException if the documnent can not 
	 *	accept the new modification state.
	 */
	public void setModified(boolean state)
		throws java.lang.IllegalStateException
	{
		if (isModifiable() == false) {
			throw new java.lang.IllegalStateException(
				"The document \"" + mDocumentURL + "\" is not modifiable.");
		}
		try {
			mModifiable.setModified(state);
		} catch (com.sun.star.beans.PropertyVetoException exp) {
			throw new java.lang.IllegalStateException(exp.getMessage());
		}
	}

	/**
	 * Retrives the office document modification state.
	 *
	 * @return The document modification state.
	 */
	public boolean isModified()
	{
		return (isModifiable() != false)? mModifiable.isModified(): false;
	}

	/**
	 * Opens the connection.
	 */
	public synchronized void openConnection()
		throws com.sun.star.uno.Exception
	{
		if (mWindow != null)
			return;

		// Establish the connection by request of the ServiceFactory.
		XMultiComponentFactory	compfactory;
		compfactory	= mConnection.getComponentContext().getServiceManager();
		mServiceFactory	= (XMultiServiceFactory)UnoRuntime.queryInterface(
			XMultiServiceFactory.class, compfactory);

		// remove existing child windows
		removeAll();

		// make initially invisible (just to test bean creation without system window)
		setVisible(false);

		// Create the OfficeWindow.
		mWindow	= mConnection.createOfficeWindow(this);
		add(mWindow.getAWTComponent());

		// Create the office document frame and initialize the bean.
		initialize();
	}

	/**
	 * Closes the connection.
	 */
	public synchronized void closeConnection()
	{
		mConnection.dispose();
	}

	/**
	 * Recieves notification of the connection closed event.
	 *
	 * If a subclass wants to be notified on connection closed event it  
	 * should add a listener to the connection object.
	 */
	private synchronized void connectionClosed()
	{
		if (mWindow != null) 
		{

			// close the frame
			try
			{
				XCloseable xCloseable = (XCloseable)UnoRuntime.queryInterface(
					XCloseable.class, mFrame);
				xCloseable.close(true);
			}
			catch ( DisposedException aExc )
			{
				// add your error handling code here
			}
			catch ( CloseVetoException aExc )
			{
				// add your error handling code here
			}

			// Clean up
			mFrame = null;
			mModifiable = null;
			mDispatcher = null;
			mTypeDetector = null;
			mURLTransformer = null;
			mServiceFactory = null;
			remove((Component)mWindow);
			mWindow = null;
		}
	}

	/**
	 * This class is a connection life-cycle event listener.
	 */
	private class ConnectionListener
		implements XEventListener
	{
		/**
		 * Receives a notification about the connection has been closed.
		 *
		 * @param source The event object.
		 */
		public void disposing(EventObject source)
		{
			connectionClosed();
		}
	}

	/**
	 * Office command execution conveyor.
	 */
	private class CommandConveyor
		extends Thread
	{
		private final List	mQueue	= new LinkedList();

		/**
		 * Appends a command to the tail of the queue.
		 *
		 * @param command The office command to add to the queue.
		 */
		public void queue(OfficeCommand command)
		{
			synchronized (mQueue) {
				mQueue.add(command);
				mQueue.notify();
			}
		}

		/**
		 * Executes commands from the queue.
		 */
		public void run()
		{
			OfficeCommand	command;
			while (true) {
				try {
					synchronized (mQueue) {
						if (mQueue.isEmpty() == true) {
							mQueue.wait();
						}
						command	= (OfficeCommand)mQueue.remove(0);
					}
					command.execute(BasicOfficeBean.this);
				} catch (java.lang.InterruptedException exp) {
					// Wait has been interrupted.
					// Do nothing, but do not execute a command which 
					// does not exist.
				}
			}
		}
	}

	/**
	 * Initializes the bean.
	 *
	 * @exception com.sun.star.uno.Exception if the frame has not been 
	 *	created or the slot command execution environment initialization 
	 *	has failed.
	 */
	private void initialize()
		throws com.sun.star.uno.Exception
	{
		Object	object;

		// Create the document frame from UNO window. (<= 6.0 => Task, >= 6.1 => Frame)
		XWindow	window	= (XWindow) UnoRuntime.queryInterface(
			XWindow.class, mWindow.getUNOWindowPeer());
		object  = mServiceFactory.createInstance( "com.sun.star.frame.Task");
		if ( object == null )
			object  = mServiceFactory.createInstance( "com.sun.star.frame.Frame");
		mFrame = (XFrame)UnoRuntime.queryInterface( XFrame.class, object );
		mFrame.initialize(window);
		mFrame.setName( mFrame.toString() );
		mDesktop = mServiceFactory.createInstance( "com.sun.star.frame.Desktop");
		XFrames xFrames = ( (XFramesSupplier)UnoRuntime.queryInterface( 
				XFramesSupplier.class, mDesktop ) ).getFrames();
		xFrames.append( mFrame );

		// Initializes the slot command execution environment.
		object	= mServiceFactory.createInstance( "com.sun.star.util.URLTransformer");
		mURLTransformer	= (XURLTransformer)UnoRuntime.queryInterface(
			XURLTransformer.class, object);

		object	= mServiceFactory.createInstance(
			"com.sun.star.document.TypeDetection");
		mTypeDetector	= (XTypeDetection)UnoRuntime.queryInterface(
			XTypeDetection.class, object);

		mDispatcher	= (XDispatchProvider)UnoRuntime.queryInterface(
			XDispatchProvider.class, mFrame);
	}

	/**
	 * Frees up resources.
	 */
	protected void finalize()
		throws Throwable
	{
		super.finalize();
		if (mConnection != null)
			mConnection.dispose();
	}

	/**
	 * Default appearance of the bean.
	 */
	private class PlaceHolder
		extends Canvas
	{
		public void paint(Graphics g)
		{
			g.setColor(Color.white);
			Rectangle	rect	= getBounds();
			g.fillRect(rect.x, rect.y, rect.width, rect.height);
			g.setColor(new Color(0x666699));
			rect.setBounds(rect.x + 1, rect.y + 1, 
				rect.width - 3, rect.height - 3);
			g.drawRect(rect.x, rect.y, rect.width, rect.height);
			rect.setBounds(rect.x + 2, rect.y + 2, 
				rect.width - 4, rect.height - 4);
			g.drawRect(rect.x, rect.y, rect.width, rect.height);
			g.drawString(BasicOfficeBean.this.getClass().getName(), 
				rect.x + 10, rect.y + 15);
		}
	}
}

