/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_framework.hxx"
#include <dispatch/helpagentdispatcher.hxx>
#include <threadhelp/readguard.hxx>
#include <threadhelp/writeguard.hxx>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/PosSize.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/awt/Rectangle.hpp>
#include <toolkit/helper/vclunohelper.hxx>
#include <svtools/helpopt.hxx>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>

namespace css = ::com::sun::star;

//........................................................................
namespace framework
{

//-----------------------------------------------
DEFINE_XINTERFACE_4(HelpAgentDispatcher                         ,
                    OWeakObject                                 ,
                    DIRECT_INTERFACE (css::lang::XTypeProvider ),
                    DIRECT_INTERFACE (css::frame::XDispatch    ),
                    DIRECT_INTERFACE (css::awt::XWindowListener),
                    DIRECT_INTERFACE (css::lang::XEventListener))

//-----------------------------------------------
DEFINE_XTYPEPROVIDER_2(HelpAgentDispatcher     ,
                       css::lang::XTypeProvider,
                       css::frame::XDispatch   )

//--------------------------------------------------------------------
HelpAgentDispatcher::HelpAgentDispatcher( const css::uno::Reference< css::frame::XFrame >& xParentFrame)
    : ThreadHelpBase	(&Application::GetSolarMutex())
    , m_sCurrentURL		(							  )
    , m_xContainerWindow(							  )
    , m_xAgentWindow	(							  )
    , m_aTimer			(							  )
    , m_xSelfHold		(							  )
{
    // It's required that this class has to be contructed with a valid frame.
    // And "valid" means: the frame must already bound to a valid container window.
    m_xContainerWindow = xParentFrame->getContainerWindow();
}

//--------------------------------------------------------------------
HelpAgentDispatcher::~HelpAgentDispatcher()
{
    implts_stopTimer();
    implts_ignoreCurrentURL();
    
    // Needed ... because it was create as "new VCLWindow()" ! Such windows must be disposed explicitly.
    css::uno::Reference< css::lang::XComponent > xAgentWindow(m_xAgentWindow, css::uno::UNO_QUERY);
    if (xAgentWindow.is())
        xAgentWindow->dispose();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::dispatch(const css::util::URL& 								   aURL ,
                                            const css::uno::Sequence< css::beans::PropertyValue >&)
    throw(css::uno::RuntimeException)
{
    // silently drop the request if the new URL was marked to be ignored next time.
    sal_Int32 nAllowedToIgnore = SvtHelpOptions().getAgentIgnoreURLCounter(aURL.Complete);
    if (nAllowedToIgnore < 1)
        return;

    // stop the expiration timer for the old URL
    // The timer will add the old URL to the list of ignorable URLs.
    // So m_sCurrentURL must be set AFTER the timer was stopped !!!
    implts_stopTimer();
    
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    m_sCurrentURL = aURL.Complete;
    aWriteLock.unlock();
    // <- SAFE
    
    // start the expiration timer for the new URL
    implts_startTimer();

    // make sure the agent window is shown
    implts_showAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >&,
                                                     const css::util::URL&)
    throw(css::uno::RuntimeException)
{
    // no status available
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >&,
                                                        const css::util::URL&)
    throw(css::uno::RuntimeException)
{
    // no status available
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowResized(const css::awt::WindowEvent&)
    throw(css::uno::RuntimeException)
{
    implts_positionAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowMoved(const css::awt::WindowEvent&)
    throw(css::uno::RuntimeException)
{
    implts_positionAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowShown(const css::lang::EventObject&)
    throw(css::uno::RuntimeException)
{
    implts_showAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowHidden(const css::lang::EventObject&)
    throw(css::uno::RuntimeException)
{
    implts_hideAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::disposing(const css::lang::EventObject& aEvent)
    throw(css::uno::RuntimeException)
{
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    
    // Already disposed ?!
    if (! m_xContainerWindow.is())
        return;
    // Wrong broadcaster ?!
    if (aEvent.Source != m_xContainerWindow)
        return;
    
    css::uno::Reference< css::uno::XInterface > xSelfHoldUntilMethodEnds(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
    m_xSelfHold.clear();
    
    aWriteLock.unlock();
    // <- SAFE
    
    implts_stopTimer();
    implts_hideAgentWindow();
    implts_ignoreCurrentURL();
    
    // SAFE ->
    aWriteLock.lock();
    m_xContainerWindow.clear();
    css::uno::Reference< css::lang::XComponent > xAgentWindow(m_xAgentWindow, css::uno::UNO_QUERY);
    m_xAgentWindow.clear();
    aWriteLock.unlock();
    // <- SAFE
    
    // Needed ... because it was create as "new VCLWindow()" ! Such windows must be disposed explicitly.
    if (xAgentWindow.is())
        xAgentWindow->dispose();
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::helpRequested()
{
    implts_stopTimer();
    implts_hideAgentWindow();
    implts_acceptCurrentURL();
}

//-----------------------------------------------
void HelpAgentDispatcher::closeAgent()
{
    implts_stopTimer();
    implts_hideAgentWindow();
    implts_ignoreCurrentURL();
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_acceptCurrentURL()
{
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    
    ::rtl::OUString sAcceptedURL  = m_sCurrentURL;
                    m_sCurrentURL = ::rtl::OUString();
    
    aWriteLock.unlock();
    // <- SAFE
    
    // We must make sure that this URL isnt marked as ignored by the user.
    // Otherwhise the user wont see the corresponding help content in the future.
    SvtHelpOptions().resetAgentIgnoreURLCounter(sAcceptedURL);

    // show the right help content
    // SOLAR SAFE ->
    {
        SolarMutexGuard aSolarLock;
        Help* pHelp = Application::GetHelp();
        if (pHelp)
            pHelp->Start(sAcceptedURL, NULL);
    }
    // <- SOLAR SAFE
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_ignoreCurrentURL()
{
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    
    ::rtl::OUString sIgnoredURL   = m_sCurrentURL;
                    m_sCurrentURL = ::rtl::OUString();
    
    aWriteLock.unlock();
    // <- SAFE
    
    if (sIgnoredURL.getLength())
        SvtHelpOptions().decAgentIgnoreURLCounter(sIgnoredURL);
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_stopTimer()
{
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    m_xSelfHold.clear();
    aWriteLock.unlock();
    // <- SAFE
    
    // SOLAR SAFE ->
    // Timer access needs no "own lock" ! It lives if we live ...
    // But it requires locking of the solar mutex ... because it's a vcl based timer.
    {
        SolarMutexGuard aSolarLock;
        if (! m_aTimer.IsActive())
            return;
        m_aTimer.Stop();
    }
    // <- SOLAR SAFE
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_startTimer()
{
    // SOLAR SAFE ->
    // Timer access needs no "own lock" ! It lives if we live ...
    // But it requires locking of the solar mutex ... because it's a vcl based timer.
    {
        SolarMutexGuard aSolarLock;
        if (m_aTimer.IsActive())
            return;
    }
    // <- SOLAR SAFE
    
    // SAFE ->
    // Timer uses pointer to this help agent dispatcher ...
    // But normaly we are ref counted. So we must make sure that this
    // dispatcher isnt killed during the timer runs .-)
    WriteGuard aWriteLock(m_aLock);
    m_xSelfHold = css::uno::Reference< css::uno::XInterface >(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
    aWriteLock.unlock();
    // <- SAFE
    
    sal_Int32 nTime = SvtHelpOptions().GetHelpAgentTimeoutPeriod();
    
    // SOLAR SAFE ->
    // Timer access needs no "own lock" ! It lives if we live ...
    // But it requires locking of the solar mutex ... because it's a vcl based timer.
    {
        SolarMutexGuard aSolarLock;
        m_aTimer.SetTimeout(nTime*1000); // sec => ms !
        m_aTimer.Start();
    }
}

//-----------------------------------------------
IMPL_LINK(HelpAgentDispatcher, implts_timerExpired, void*,)
{
    // This method is called by using a pointer to us.
    // But we must be aware that we can be destroyed hardly
    // if our uno reference will be gone!
    // => Hold this object alive till this method finish its work.
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    css::uno::Reference< css::uno::XInterface > xSelfHoldUntilMethodEnds(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
    m_xSelfHold.clear();
    aWriteLock.unlock();
    // <- SAFE

    implts_hideAgentWindow();
    implts_ignoreCurrentURL();
    
    return 0;
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_showAgentWindow()
{
    // SAFE ->
    ReadGuard aReadLock(m_aLock);
    css::uno::Reference< css::awt::XWindow2 > xContainerWindow(m_xContainerWindow, css::uno::UNO_QUERY_THROW);
    aReadLock.unlock();
    // <- SAFE
    
    css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
    
    if (
        (xContainerWindow.is() 		  ) &&
        (xAgentWindow.is() 		  	  ) &&
        (xContainerWindow->isVisible())
       )
    {
        // make sure that agent window resists at the right place .-)
        implts_positionAgentWindow();
        xAgentWindow->setVisible(sal_True);
    }
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_hideAgentWindow()
{
    css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
    if (xAgentWindow.is())
        xAgentWindow->setVisible(sal_False);
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_positionAgentWindow()
{
    // SAFE ->
    ReadGuard aReadLock(m_aLock);
    css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
    aReadLock.unlock();
    // <- SAFE
    
    css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
    if (
        (! xContainerWindow.is())  ||
        (! xAgentWindow.is()    )
       )
        return;
    
          ::svt::HelpAgentWindow* pAgentWindow   = (::svt::HelpAgentWindow*)VCLUnoHelper::GetWindow(xAgentWindow);
    const css::awt::Rectangle 	  aContainerSize = xContainerWindow->getPosSize();
    const Size                	  aAgentSize     = pAgentWindow->getPreferredSizePixel();

    sal_Int32 nW = aAgentSize.Width() ;
    sal_Int32 nH = aAgentSize.Height();

    if (nW < 1)
        nW = 100;
    if (nH < 1)
        nH = 100;

    sal_Int32 nX = aContainerSize.Width  - nW;
    sal_Int32 nY = aContainerSize.Height - nH;

    // TODO: use a surrogate if the container window is too small to contain the full-sized agent window
    xAgentWindow->setPosSize(nX, nY, nW, nH, css::awt::PosSize::POSSIZE);
}

//--------------------------------------------------------------------
css::uno::Reference< css::awt::XWindow > HelpAgentDispatcher::implts_ensureAgentWindow()
{
    // SAFE ->
    ReadGuard aReadLock(m_aLock);
    if (m_xAgentWindow.is())
        return m_xAgentWindow;
    css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
    aReadLock.unlock();
    // <- SAFE
    
    if (!xContainerWindow.is())
        return css::uno::Reference< css::awt::XWindow >();
    
    ::svt::HelpAgentWindow* pAgentWindow = 0;
    // SOLAR SAFE ->
    {
        SolarMutexGuard aSolarLock;
        // create the agent window
        Window*	pContainerWindow = VCLUnoHelper::GetWindow(xContainerWindow);
                pAgentWindow 	 = new ::svt::HelpAgentWindow(pContainerWindow);
        pAgentWindow->setCallback(this);
    }
    // <- SOLAR SAFE
    
    // SAFE ->
    WriteGuard aWriteLock(m_aLock);
    m_xAgentWindow = VCLUnoHelper::GetInterface(pAgentWindow);
    css::uno::Reference< css::awt::XWindow > xAgentWindow = m_xAgentWindow;
    aWriteLock.unlock();
    // <- SAFE
    
    // add as window listener to the container window so we can maintain the property position of the agent window
    xContainerWindow->addWindowListener(this);

    // SOLAR SAFE ->
    {
        SolarMutexGuard aSolarLock;
        // establish callback for our internal used timer.
        // Note: Its only active, if the timer will be started ...
        m_aTimer.SetTimeoutHdl(LINK(this, HelpAgentDispatcher, implts_timerExpired));
    }
    // <- SOLAR SAFE
    
    return xAgentWindow;
}

} // namespace framework

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