/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 *  The Contents of this file are made available subject to the terms of
 *  the BSD license.
 *
 *  Copyright 2000, 2010 Oracle and/or its affiliates.
 *  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.
 *
 *************************************************************************/

// __________ Imports __________

import java.awt.Component;
import java.util.ArrayList;

import javax.swing.JCheckBox;
import javax.swing.JLabel;

import com.sun.star.frame.FrameActionEvent;
import com.sun.star.uno.UnoRuntime;

// __________ Implementation __________

/**
 * reacts for status events we listen for
 * We listen for status events to update our UI.
 * To know which event must be used for which UI control
 * we use a special class to do that. Otherwise we have
 * to guess it ...
 *
 * Further we are frame action listener too.
 * So we can update our status listener connections and
 * internal holded dispatch object automatically.
 *
 * Another reason for such extra class for listening:
 * Most listener callbacks are asynchronous [oneay] requests.
 * And it's not allowed to call back synchronously there.
 * So we must start threads for updating something internally.
 *
 */
class StatusListener implements com.sun.star.frame.XStatusListener,
                                com.sun.star.frame.XFrameActionListener,
                                IShutdownListener,
                                IOnewayLink
{


    /**
     * @member m_rControl           reference to the UI control, which should be updated
     * @member m_sTrueText          this text will be shown at the used UI control as description for an enabled status
     * @member m_sFalseText         this text will be shown at the used UI control as description for an disabled status
     * @member m_xDispatch          if we listen for status events, we must hold the dispatch object alive!
     * @member m_xFrame             reference to the frame, which can provide new dispatch objects if it's necessary to update it
     * @member m_aURL               and of course we must be registered for a special URL
     * @member m_bIsActionListener  indicates if we are currently registered as a listener for frame action events or not
     * @member m_bIsStatusListener  indicates if we are currently registered as a listener for status events or not
     * @member m_bDead              there exist more than one way to finish an object of this class - we must know it sometimes
     */
    private final Component                    m_rControl         ;
    private final String                       m_sTrueText        ;
    private final String                       m_sFalseText       ;
    private com.sun.star.frame.XDispatch m_xDispatch        ;
    private com.sun.star.frame.XFrame    m_xFrame           ;
    private com.sun.star.util.URL        m_aURL             ;
    private boolean                      m_bIsActionListener;
    private boolean                      m_bIsStatusListener;
    private boolean                      m_bDead            ;



    /**
     * ctor
     * It initialize an instance of this class only.
     * We set all necessary information on our internal member - that's it
     */
    StatusListener( /*IN*/ Component                   rControl   ,
                    /*IN*/ String                      sTrueText  ,
                    /*IN*/ String                      sFalseText ,
                    /*IN*/ com.sun.star.frame.XFrame   xFrame     ,
                    /*IN*/ String                      sURL       )
    {
        m_rControl          = rControl   ;
        m_sTrueText         = sTrueText  ;
        m_sFalseText        = sFalseText ;
        m_xFrame            = xFrame     ;
        m_bIsStatusListener = false      ;
        m_bIsActionListener = false      ;
        m_bDead             = false      ;
        // to be perform - we parse the given URL  one times only
        // and use it till we die ...
        m_aURL = FunctionHelper.parseURL(sURL);
    }



    /**
     * start working as frame action listener really.
     * In case we get such frame action, it indicates that we should
     * update our internal saved dispatch object on which we listen
     * for status events. So we can do it automatically. The outside code
     * mustn't check such things. We can work with one frame,
     * till it die. It doesn't matter if he will be used for different
     * load/save or any other requests. We will be up to date every time.
     */
    public void startListening()
    {
        com.sun.star.frame.XFrame xFrame = null;
        synchronized(this)
        {
            if (m_bDead)
                return;
            if (m_xFrame==null)
                return;
            if (m_bIsActionListener)
                return;
            xFrame = m_xFrame;
        }
        xFrame.addFrameActionListener(this);
        synchronized(this)
        {
            m_bIsActionListener=true;
        }
    }



    /**
     * In case we got a oneway listener callback - we had to use the office
     * asynchronous then. This method is the callback from the started thread
     * (started inside the original oneway method). We found all parameters of
     * the original request packed inside a vector. Here we unpack it and
     * call the right internal helper method, which implements the right
     * functionality.
     *
     * @seealso frameAction()
     * @seealso statusChanged()
     *
     * @param nRequest
     *          indicates, which was the original request (identifies the
     *          original called method)
     *
     * @param lParams
     *          the vector with all packed parameters of the original request
     */
    public void execOneway(/*IN*/  int nRequest,/*IN*/  ArrayList<Object> lParams )
    {
        synchronized(this)
        {
            if (m_bDead)
                return;
        }
        // was it frameAction()?
        if (nRequest==OnewayExecutor.REQUEST_FRAMEACTION)
        {
            impl_frameAction((FrameActionEvent) lParams.get(0));
        }
    }



    /**
     * This is the callback method for such frame action events, we listen for.
     * Because it's a oneway method we start a thread as reaction. This thread call
     * us back and we can do necessary things there.
     * But we shouldn't start such action - if it's not really necessary.
     * So we check before, if we are interested on this event really.
     *
     * @see #impl_frameAction
     *
     * @param aEvent
     *          describes the action, which triggered this event
     */
    public /*ONEWAY*/ void frameAction(/*IN*/ com.sun.star.frame.FrameActionEvent aEvent)
    {
        synchronized(this)
        {
            if (m_bDead)
                return;
        }
        boolean bHandle = false;
        switch(aEvent.Action.getValue())
        {
            case com.sun.star.frame.FrameAction.COMPONENT_ATTACHED_value   : bHandle=true; break;
            case com.sun.star.frame.FrameAction.COMPONENT_DETACHING_value  : bHandle=true; break;
            case com.sun.star.frame.FrameAction.COMPONENT_REATTACHED_value : bHandle=true; break;
            case com.sun.star.frame.FrameAction.CONTEXT_CHANGED_value      : bHandle=true; break;
        }

        if (! bHandle)
            return;

        ArrayList<Object> lOutParams   = new ArrayList<Object>();
        lOutParams.add(aEvent);

        OnewayExecutor aExecutor = new OnewayExecutor( this                  ,
                                                       OnewayExecutor.REQUEST_FRAMEACTION ,
                                                       lOutParams                      );
        aExecutor.start();
    }



    /**
     * This is the callback method for the status we listen for and wish to show it
     * on our UI control. Of course it's a oneway method... but we don't call back
     * to the office synchronously here. We update our UI only. So we don't leave this
     * java process. In such case it's not necessary to use threads to decouple it.
     * Do it here and now...
     *
     * @param aEvent
     *          describes the status, we can use to update our UI control
     */
    public /*ONEWAY*/ void statusChanged(/*IN*/ com.sun.star.frame.FeatureStateEvent aEvent)
    {
        synchronized(this)
        {
            if (m_bDead)
                return;

            // enable/disable the control.
            // Means: if the feature isn't available currently - we can't show a status really here.
            // Then we should colorize it gray...
            m_rControl.setEnabled(aEvent.IsEnabled);

            // Only if status is enabled we can look for his value!
            if (aEvent.IsEnabled)
            {
                // look for the right type of the UI control
                // Following actions depend on it.


                // it's a check box
                if (m_rControl instanceof JCheckBox)
                {
                    JCheckBox aBox = (JCheckBox)m_rControl;

                    // State must be a boolean value too. Otherwise must
                    // ignore this event.
                    if ( ! (aEvent.State instanceof Boolean ) )
                        return;

                    boolean bState = ((Boolean)(aEvent.State)).booleanValue();
                    aBox.setSelected(bState);
                    if (bState)
                        aBox.setText(m_sTrueText);
                    else
                        aBox.setText(m_sFalseText);
                }
                else

                // it's a label
                if (m_rControl instanceof JLabel)
                {
                    JLabel aLabel = (JLabel)m_rControl;

                    // Detect type of state value
                    // and set it on internal well known UI control
                    // But do it only, if value really change.
                    if(aEvent.State instanceof String)
                    {
                        String sState = (String)aEvent.State;
                        aLabel.setText(sState);
                    }
                    else
                    if(aEvent.State instanceof Boolean)
                    {
                        boolean bState = ((Boolean)aEvent.State).booleanValue();
                        if (bState)
                            aLabel.setText(m_sTrueText);
                        else
                            aLabel.setText(m_sFalseText);
                    }
                    else
                    if(aEvent.State instanceof Float)
                    {
                        String sState = ((Float)aEvent.State).toString();
                        aLabel.setText(sState);
                    }
                }
            }
        }
    }



    /**
     * Internal call back for frame action events, triggered by the used
     * OnewayExecutor thread we started in frameAction().
     * We use it to update internal saved dispatch object and the corresponding
     * listener connection for status events.
     *
     * @param aEvent
     *          describes the action
     */
    private void impl_frameAction(/*IN*/ com.sun.star.frame.FrameActionEvent aEvent)
    {
        synchronized(this)
        {
            if (m_bDead)
                return;
        }
        // Don't look for ignoring actions - it was done already inside original frameAction() call!
        // deregistration as status listener will be done here every time - but registration only, if necessary!
        boolean bRegister = false;
        switch(aEvent.Action.getValue())
        {
            case com.sun.star.frame.FrameAction.COMPONENT_ATTACHED_value   : bRegister=true ; break;
            case com.sun.star.frame.FrameAction.COMPONENT_DETACHING_value  : bRegister=false; break;
            case com.sun.star.frame.FrameAction.COMPONENT_REATTACHED_value : bRegister=true ; break;
            case com.sun.star.frame.FrameAction.CONTEXT_CHANGED_value      : bRegister=true ; break;
        }

        boolean                      bIsStatusListener = false;
        com.sun.star.frame.XFrame    xFrame            = null ;
        com.sun.star.frame.XDispatch xDispatch         = null ;
        com.sun.star.util.URL        aURL              = null ;
        synchronized(this)
        {
            bIsStatusListener   = m_bIsStatusListener;
            m_bIsStatusListener = false;

            xDispatch   = m_xDispatch;
            m_xDispatch = null;

            aURL        = m_aURL;
            xFrame      = m_xFrame;
        }

        if (bIsStatusListener)
            xDispatch.removeStatusListener(this,aURL);
        xDispatch = null;

        if (! bRegister)
            return;

        com.sun.star.frame.XDispatchProvider xProvider = UnoRuntime.queryInterface(
            com.sun.star.frame.XDispatchProvider.class,
            xFrame);

        if (xProvider==null)
            return;

        xDispatch = xProvider.queryDispatch(aURL,"",0);

        if (xDispatch==null)
            return;

        xDispatch.addStatusListener(this,aURL);
        synchronized(this)
        {
            m_xDispatch         = xDispatch;
            m_bIsStatusListener = true;
        }
    }



    /**
     * callback for disposing events
     * Our dispatch or frame object inform us about his following dead ...
     * So we must forget his reference. But it's not necessary to
     * remove listener connections here. Because the broadcaster
     * forget us automatically. The only thing we have to do: release
     * his reference and let him die!
     *
     * @param aEvent
     *          describes the source which fire this event
     *          Must be our internal saved dispatch or frame. Otherwise
     *          somewhere know us without a registration ...
     */
    public /*ONEWAY*/ void disposing(/*IN*/ com.sun.star.lang.EventObject aEvent)
    {
        synchronized(this)
        {
            if (m_bDead)
                return;
            if (m_xFrame!=null && UnoRuntime.areSame(aEvent.Source,m_xFrame))
            {
                m_bIsActionListener = false;
                m_xFrame            = null ;
            }
            else
            if (m_xDispatch!=null && UnoRuntime.areSame(aEvent.Source,m_xDispatch))
            {
                m_bIsStatusListener = false;
                m_xDispatch         = null ;
                m_aURL              = null ;
            }
        }
        shutdown();
    }



    /**
     * If this java application shutdown - we must cancel all current existing
     * listener connections. Otherwise the office will run into some
     * DisposedExceptions if it tries to use these forgotten listener references.
     * And of course it can die doing that.
     * We are registered at a central object to be informed if the VM will exit.
     * So we can react.
     */
    public void shutdown()
    {
        boolean                      bIsActionListener = false;
        boolean                      bIsStatusListener = false;
        com.sun.star.frame.XFrame    xFrame            = null ;
        com.sun.star.frame.XDispatch xDispatch         = null ;
        com.sun.star.util.URL        aURL              = null ;
        synchronized(this)
        {
            // don't react a second time here!
            if (m_bDead)
                return;
            m_bDead = true;

            bIsActionListener   = m_bIsActionListener;
            m_bIsActionListener = false;

            bIsStatusListener   = m_bIsStatusListener;
            m_bIsStatusListener = false;

            xFrame              = m_xFrame;
            m_xFrame            = null;

            xDispatch           = m_xDispatch;
            m_xDispatch         = null;

            aURL                = m_aURL;
            m_aURL              = null;
        }

        if (bIsStatusListener)
            xDispatch.removeStatusListener(this,aURL);
        xDispatch = null ;
        aURL      = null ;

        if (bIsActionListener)
            xFrame.removeFrameActionListener(this);
        xFrame = null ;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
