/*
* Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  System for managing global popup window priorities
*
*/


#include "aknglobalpopupprioritycontroller.h"
#include "AknPopupFader.h"
#include <eikenv.h>
#include <e32hashtab.h>
#include <coecntrl.h>
#include <eikappui.h>

const TUid KAknGlobalPopupPriorityControllerStaticId = { 0x102835a0 };
const TInt KAknGlobalPopupPriorityControllerMaxStack = 10;

const TInt KControlStackSleepFlags = ECoeStackFlagRefusesAllKeys|ECoeStackFlagRefusesFocus;

enum TGppcPanic
    {
    EGppcPanic_DontSupportMultipleSubPopups,
    EGppcPanic_DontSupportMultipleParents
    };

void GppcPanic(TGppcPanic aReason)
    {
    _LIT(KReason, "AknGppc");
    User::Panic(KReason, aReason);
    }


// hash an identity functions required by RHashMap
template<typename T>
TUint32 GeneralPtrHash(T* const & aPtr)
    {
    return DefaultHash::Integer((TInt)aPtr);
    }

template<typename T>
TBool GeneralPtrIdentity(T* const & aP1, T* const & aP2)
    {
    return aP1 == aP2;
    }


NONSHARABLE_CLASS(CAknGlobalPopupPriorityController) : public CCoeStatic
    {
public:
    static void CreateInstanceL();
    static CAknGlobalPopupPriorityController* Instance();

    void AllowGlobalPopups(TBool aAllow);
    void SetPopupPriorityL(CCoeControl* aPopup, TInt aPriority);
    void RemovePopup(CCoeControl* aPopup);
    void ShowPopup(CCoeControl* aPopup, TBool aShow);
    void SetRootWinOrdinalPosition(TInt aOrdinalPosition);
    void AddSubPopupL(CCoeControl* aPopup, CCoeControl* aSubPopup);
    void AddPopupToControlStackL(CCoeControl* aPopup,TInt aPriority,TInt aStackingFlags);
    void FadeBehindPopupL(CCoeControl* aPopup, MAknFadedComponent* aFadedComponent);
    void RouseSleepingPopup(CCoeControl* aPopup, TBool aRoused);

    TBool GlobalPopupsAllowed();
private:
    CAknGlobalPopupPriorityController();
    ~CAknGlobalPopupPriorityController();
    void ConstructL();

    TInt PopupPriority(CCoeControl* aPopup) const;
    TBool AddToStack(CCoeControl* aPopup);
    TBool RemoveFromStack(CCoeControl* aPopup);
    void SetWindowGroupPriority();
    void SetPopupWindowPositions(CCoeControl* aPopup, TInt aWindowPos);
    void SetPopupChainKeyPositionsL(CCoeControl* aPopup);
    void SetPopupKeyPositionsL(CCoeControl* aPopup);
    CCoeControl* RootPopup(CCoeControl* aTip) const;
    CCoeControl* TopPopup() const;
    TBool PopupIsShown(CCoeControl* aPopup) const;
    void DoFadeBehindPopup(CCoeControl* aPopup);
    TInt MinChainWindowPosition(CCoeControl* aPopup) const;

private:
    struct TPopupInfo
        {
    public:
        TPopupInfo();

    public:
        TInt iPopupPriority;
        TInt iControlStackPriority;
        TInt iControlStackFlags;
        CCoeControl* iSubPopup;     // not owned
        CCoeControl* iParent;       // not owned
        MAknFadedComponent* iFadedComponent;    // not owned
        };

private:
    TPopupInfo* PopupInfo(CCoeControl* aPopup);
    const TPopupInfo* PopupInfo(CCoeControl* aPopup) const;
    TPopupInfo& PopupInfoL(CCoeControl* aPopup);

private:
    RHashMap<CCoeControl*, TPopupInfo> iPopupInfoMap;
    // The stack of currently shown popups (the CCoeControls are not owned)
    RArray<CCoeControl*> iShownStack;
    TBool iGlobalPopupsAllowed;
    TInt iOrdinalPosition;
    CEikAppUi* iAppUi;  // not owned
    };


CAknGlobalPopupPriorityController::TPopupInfo::TPopupInfo()
: iPopupPriority(0), iControlStackPriority(KErrNotFound), iControlStackFlags(0), iSubPopup(NULL), iParent(NULL), iFadedComponent(NULL)
    {
    }


void CAknGlobalPopupPriorityController::CreateInstanceL()
    {
    if (!CAknGlobalPopupPriorityController::Instance())
        {
        CAknGlobalPopupPriorityController* self = new(ELeave) CAknGlobalPopupPriorityController(); // CCoeEnv takes ownership immediately
        self->ConstructL();
        }
    }

CAknGlobalPopupPriorityController* CAknGlobalPopupPriorityController::Instance()
    {
    return static_cast<CAknGlobalPopupPriorityController*>(CCoeEnv::Static(KAknGlobalPopupPriorityControllerStaticId));
    }

void CAknGlobalPopupPriorityController::AllowGlobalPopups(TBool aAllow)
    {
    iGlobalPopupsAllowed = aAllow;
    SetWindowGroupPriority();
    CCoeControl* top = TopPopup();
    if (top)
        {
        TRAP_IGNORE(SetPopupChainKeyPositionsL(top));
        DoFadeBehindPopup(top);
        }
    }

TBool CAknGlobalPopupPriorityController::GlobalPopupsAllowed()
    {
    return iGlobalPopupsAllowed;
    }

void CAknGlobalPopupPriorityController::SetPopupPriorityL(CCoeControl* aPopup, TInt aPriority)
    {
    PopupInfoL(aPopup).iPopupPriority = aPriority;

    // Update the active popup stack
    if (PopupIsShown(aPopup))
        {
        ShowPopup(aPopup, ETrue);
        }
    }

void CAknGlobalPopupPriorityController::RemovePopup(CCoeControl* aPopup)
    {
    // Update the active popup stack
    if (PopupIsShown(aPopup))
        {
        ShowPopup(aPopup, EFalse);
        }

    // Remove sub-popup from parent
    TPopupInfo* info = PopupInfo(aPopup);
    if (info && info->iParent)
        {
        TPopupInfo* pInfo = PopupInfo(info->iParent);
        if (pInfo)
            pInfo->iSubPopup = NULL;
        }

    // Remove the popup and sub popups from the map
    CCoeControl* popup = aPopup;
    while (popup)
        {
        CCoeControl* next = NULL;
        TPopupInfo* info = PopupInfo(popup);
        if (info)
            next = info->iSubPopup;
        iPopupInfoMap.Remove(popup);
        popup = next;
        }
    }

TInt CAknGlobalPopupPriorityController::PopupPriority(CCoeControl* aPopup) const
    {
    const TPopupInfo* info = PopupInfo(aPopup);
    return info ? info->iPopupPriority : 0;
    }

void CAknGlobalPopupPriorityController::ShowPopup(CCoeControl* aPopup, TBool aShow)
    {
    // update the stack
    CCoeControl* oldTop = TopPopup();
    if (aShow)
        {
        AddToStack(aPopup);
        TRAP_IGNORE(SetPopupChainKeyPositionsL(aPopup));
        }
    else
        {
        RemoveFromStack(aPopup);
        }
    DoFadeBehindPopup(aPopup);
    CCoeControl* newTop = TopPopup();

    // update the top popup
    if (oldTop != newTop)
        {
        // set window group priority
        SetWindowGroupPriority();

        // an existing popup is now top
        if (newTop != aPopup)
            {
            TRAP_IGNORE(SetPopupChainKeyPositionsL(newTop));
            DoFadeBehindPopup(newTop);
            }
        }
    }

void CAknGlobalPopupPriorityController::SetRootWinOrdinalPosition(TInt aOrdinalPosition)
    {
    iOrdinalPosition = aOrdinalPosition;
    }

void CAknGlobalPopupPriorityController::SetWindowGroupPriority()
    {
    TInt newPriority;
    if (iShownStack.Count() > 0 && iGlobalPopupsAllowed)
        {
        CCoeControl* topPopup = iShownStack[0];
        TInt priority = PopupPriority(topPopup);
        newPriority = ECoeWinPriorityAlwaysAtFront + priority;
        }
    else
        {
        newPriority = ECoeWinPriorityNeverAtFront;
        }
    CEikonEnv::Static()->RootWin().SetOrdinalPosition(iOrdinalPosition, newPriority);
    }

TBool CAknGlobalPopupPriorityController::AddToStack(CCoeControl* aPopup)
    {
    // remove it from the stack if already there
    TInt priorPos = iShownStack.Find(aPopup);
    if (priorPos != KErrNotFound)
        {
        iShownStack.Remove(priorPos);
        }

    // stack size is limited, do not add new items if it would cause overflow
    TInt count = iShownStack.Count();
    if (count >= KAknGlobalPopupPriorityControllerMaxStack)
        {
        return EFalse;
        }

    // insert it at correct position in the stack
    // also calculate min and max window z-order position
    TInt priority = PopupPriority(aPopup);
    TInt insertPos = -1;
    TInt minWindowPosition = 0;
    TInt maxWindowPosition = KMaxTInt;
    for (TInt ii=0; ii<count; ii++)
        {
        CCoeControl* stacked = iShownStack[ii];
        TInt shownPriority = PopupPriority(stacked);
        if (priority >= shownPriority)
            {
            maxWindowPosition = MinChainWindowPosition(stacked);
            if (KErrNone != iShownStack.Insert(aPopup, ii))
                {
                return EFalse;
                }
            insertPos = ii;
            break;
            }
        minWindowPosition = stacked->DrawableWindow()->OrdinalPosition();
        }
    if (insertPos == -1)
        {
        insertPos = count;
        iShownStack.Append(aPopup); // will not fail because overflow is already checked
        }

    // set popup window z-order position, if necessary
    // it should be behind any higher priority popup and infront
    // of any lower priority popup
    TInt popupWindowPosition = aPopup->DrawableWindow()->OrdinalPosition();
    if (popupWindowPosition < minWindowPosition)
        popupWindowPosition = minWindowPosition+1;
    else if (popupWindowPosition > maxWindowPosition)
        popupWindowPosition = maxWindowPosition;
    SetPopupWindowPositions(aPopup, popupWindowPosition);

    // return true (top changed) if top item was moved or added
    return priorPos == 0 || insertPos == 0;
    }

void CAknGlobalPopupPriorityController::SetPopupWindowPositions(
        CCoeControl* aPopup, TInt aWindowPos)
    {
    TBool isSub = EFalse;
    while (aPopup)
        {
        TInt currentPos = aPopup->DrawableWindow()->OrdinalPosition();
        if (isSub && currentPos < aWindowPos)
            {
            // adjust one forward, so that we don't go behind the parent window
            aWindowPos--;
            }

        aPopup->DrawableWindow()->SetOrdinalPosition(aWindowPos);
        const TPopupInfo* info = PopupInfo(aPopup);
        if (info)
            {
            aPopup = info->iSubPopup;
            }
        else
            {
            aPopup = NULL;
            }
        isSub = ETrue;
        }
    }

void CAknGlobalPopupPriorityController::SetPopupChainKeyPositionsL(CCoeControl* aPopup)
    {
    bool top = aPopup == TopPopup();
    while (aPopup)
        {
        const TPopupInfo* info = PopupInfo(aPopup);
        CCoeControl* next = NULL;
        if (info && info->iControlStackPriority != KErrNotFound)
            {
            next = info->iSubPopup;
            iAppUi->RemoveFromStack(aPopup);
            TInt flags = top ? info->iControlStackFlags : KControlStackSleepFlags;
            iAppUi->AddToStackL(aPopup, info->iControlStackPriority, flags);
            }
        aPopup = next;
        }
    }

void CAknGlobalPopupPriorityController::SetPopupKeyPositionsL(CCoeControl* aPopup)
    {
    bool top = RootPopup(aPopup) == TopPopup();
    const TPopupInfo* info = PopupInfo(aPopup);
    if (info && info->iControlStackPriority != KErrNotFound)
        {
        iAppUi->RemoveFromStack(aPopup);
        TInt flags = top ? info->iControlStackFlags : KControlStackSleepFlags;
        iAppUi->AddToStackL(aPopup, info->iControlStackPriority, flags);
        }
    }

TBool CAknGlobalPopupPriorityController::RemoveFromStack(CCoeControl* aPopup)
    {
    // only if it's already in the stack
    TInt pos = iShownStack.Find(aPopup);
    if (pos == KErrNotFound)
        {
        return EFalse;
        }
    else
        {
        iShownStack.Remove(pos);
        return pos == 0;
        }
    }

CAknGlobalPopupPriorityController::CAknGlobalPopupPriorityController()
: CCoeStatic(KAknGlobalPopupPriorityControllerStaticId),
  iPopupInfoMap(THashFunction32<CCoeControl*>(&GeneralPtrHash), TIdentityRelation<CCoeControl*>(&GeneralPtrIdentity)),
  iGlobalPopupsAllowed(EFalse),
  iAppUi(CEikonEnv::Static()->EikAppUi())
    {
    }

CAknGlobalPopupPriorityController::~CAknGlobalPopupPriorityController()
    {
    iPopupInfoMap.Close();
    iShownStack.Close();
    }

void CAknGlobalPopupPriorityController::ConstructL()
    {
    iShownStack.Reserve(KAknGlobalPopupPriorityControllerMaxStack);
    }

CAknGlobalPopupPriorityController::TPopupInfo* CAknGlobalPopupPriorityController::PopupInfo(CCoeControl* aPopup)
    {
    TPopupInfo* info = iPopupInfoMap.Find(aPopup);
    return info;
    }

const CAknGlobalPopupPriorityController::TPopupInfo* CAknGlobalPopupPriorityController::PopupInfo(CCoeControl* aPopup) const
    {
    const TPopupInfo* info = iPopupInfoMap.Find(aPopup);
    return info;
    }

CAknGlobalPopupPriorityController::TPopupInfo& CAknGlobalPopupPriorityController::PopupInfoL(CCoeControl* aPopup)
    {
    TPopupInfo* info = PopupInfo(aPopup);
    if (info)
        return *info;
    iPopupInfoMap.InsertL(aPopup, TPopupInfo());
    return *PopupInfo(aPopup);
    }

void CAknGlobalPopupPriorityController::AddSubPopupL(CCoeControl* aPopup, CCoeControl* aSubPopup)
    {
    // Below 3 rows of calling PopupInfoL() look like a little uncomfortable,
    // because PopupInfoL will add new element in its hash map. 
    // So, a new element may be inserted when calling PopupInfoL(aSubPopup).
    // It may will lead to the change of aPopup address returned from the first calling PopupInfoL(aPopup).
    // So, we need to get the parent reference again.
    PopupInfoL(aPopup);
    TPopupInfo& sub = PopupInfoL(aSubPopup);
    TPopupInfo& parent = PopupInfoL(aPopup);
    
    __ASSERT_DEBUG(parent.iSubPopup == NULL || parent.iSubPopup == aSubPopup, GppcPanic(EGppcPanic_DontSupportMultipleSubPopups));
    __ASSERT_DEBUG(sub.iParent == NULL || sub.iParent == aPopup, GppcPanic(EGppcPanic_DontSupportMultipleParents));
    parent.iSubPopup = aSubPopup;
    sub.iParent = aPopup;
    // put it at the same window position as its parent
    aSubPopup->DrawableWindow()->SetOrdinalPosition(aPopup->DrawableWindow()->OrdinalPosition());
    }


void CAknGlobalPopupPriorityController::AddPopupToControlStackL(CCoeControl* aPopup,TInt aPriority,TInt aStackingFlags)
    {
    TPopupInfo& info = PopupInfoL(aPopup);
    info.iControlStackPriority = aPriority;
    info.iControlStackFlags = aStackingFlags;
    SetPopupKeyPositionsL(aPopup);
    }

CCoeControl* CAknGlobalPopupPriorityController::RootPopup(CCoeControl* aTip) const
    {
    const TPopupInfo* info;
    info = PopupInfo(aTip);
    while (info && info->iParent)
        {
        aTip = info->iParent;
        info = PopupInfo(aTip);
        }


    return aTip;
    }

CCoeControl* CAknGlobalPopupPriorityController::TopPopup() const
    {
    if (iShownStack.Count() > 0 && iGlobalPopupsAllowed)
        return iShownStack[0];
    else
        return NULL;
    }

TBool CAknGlobalPopupPriorityController::PopupIsShown(CCoeControl* aPopup) const
    {
    TInt pos = iShownStack.Find(aPopup);
    return pos != KErrNotFound;
    }

void CAknGlobalPopupPriorityController::FadeBehindPopupL(CCoeControl* aPopup, MAknFadedComponent* aFadedComponent)
    {
    TPopupInfo& info = PopupInfoL(aPopup);
    info.iFadedComponent = aFadedComponent;
    DoFadeBehindPopup(aPopup);
    }

void CAknGlobalPopupPriorityController::DoFadeBehindPopup(CCoeControl* aPopup)
    {
    TBool top = aPopup == TopPopup();
    TPopupInfo* info = PopupInfo(aPopup);
    if (!info || !info->iFadedComponent)
        return;
    TAknPopupFader fader;
    fader.FadeBehindPopup(info->iFadedComponent, aPopup, top);
    }

void CAknGlobalPopupPriorityController::RouseSleepingPopup(CCoeControl* aPopup, TBool aRoused)
    {
    TInt pri = ECoeStackPriorityDialog;
    TInt flags = 0;
    TPopupInfo* info = PopupInfo(aPopup);
    if (info)
        {
        pri = info->iControlStackPriority;
        flags = info->iControlStackFlags;
        }
    if (aRoused)
        flags = flags & ~KControlStackSleepFlags;
    else
        flags = flags | KControlStackSleepFlags;
    TRAP_IGNORE(AddPopupToControlStackL(aPopup, pri, flags));
    }

TInt CAknGlobalPopupPriorityController::MinChainWindowPosition(CCoeControl* aPopup) const
    {
    TInt pos = aPopup->DrawableWindow()->OrdinalPosition();
    const TPopupInfo* info = PopupInfo(aPopup);
    while (info && info->iSubPopup)
        {
        aPopup = info->iSubPopup;
        pos = Min(pos, aPopup->DrawableWindow()->OrdinalPosition());
        info = PopupInfo(aPopup);
        }
    return pos;
    }


EXPORT_C void AknGlobalPopupPriorityController::EnablePriorityControlL()
    {
    CAknGlobalPopupPriorityController::CreateInstanceL();
    }

EXPORT_C void AknGlobalPopupPriorityController::AllowGlobalPopups(TBool aAllow)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->AllowGlobalPopups(aAllow);
    }

EXPORT_C TBool AknGlobalPopupPriorityController::GlobalPopupsAllowed()
    {
    TBool globalPopupsAllowed = ETrue;
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        globalPopupsAllowed = instance->GlobalPopupsAllowed();
    return globalPopupsAllowed;
    }

EXPORT_C void AknGlobalPopupPriorityController::SetPopupPriorityL(CCoeControl& aPopup, TInt aPriority)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->SetPopupPriorityL(&aPopup, aPriority);
    }

EXPORT_C void AknGlobalPopupPriorityController::RemovePopupPriority(CCoeControl& aPopup)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->RemovePopup(&aPopup);
    }

EXPORT_C void AknGlobalPopupPriorityController::ShowPopup(CCoeControl& aPopup, TBool aShow)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->ShowPopup(&aPopup, aShow);
    }

EXPORT_C void AknGlobalPopupPriorityController::SetRootWinOrdinalPosition(TInt aOrdinalPosition)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->SetRootWinOrdinalPosition(aOrdinalPosition);
    }

EXPORT_C void AknGlobalPopupPriorityController::AddSubPopupL(CCoeControl& aPopup, CCoeControl& aSubPopup)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->AddSubPopupL(&aPopup, &aSubPopup);
    }

EXPORT_C void AknGlobalPopupPriorityController::AddPopupToControlStackL(CCoeControl& aPopup,TInt aPriority,TInt aStackingFlags)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->AddPopupToControlStackL(&aPopup, aPriority, aStackingFlags);
    else
        {
        CEikAppUi* appUi = CEikonEnv::Static()->EikAppUi();
        if (appUi)
            {
            appUi->RemoveFromStack(&aPopup);
            appUi->AddToStackL(&aPopup, aPriority, aStackingFlags);
            }
        }
    }

EXPORT_C void AknGlobalPopupPriorityController::RouseSleepingPopup(CCoeControl& aPopup, TBool aRoused)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        instance->RouseSleepingPopup(&aPopup, aRoused);
    else
        CEikonEnv::Static()->RouseSleepingDialog(&aPopup, aRoused);
    }

EXPORT_C void AknGlobalPopupPriorityController::FadeBehindPopup(CCoeControl& aPopup, TAknPopupFader& aFader, MAknFadedComponent& aFadedComponent, TBool aFade)
    {
    CAknGlobalPopupPriorityController* instance = CAknGlobalPopupPriorityController::Instance();
    if (instance)
        {
        TRAP_IGNORE(instance->FadeBehindPopupL(&aPopup, &aFadedComponent));
        }
    else
        {
        aFader.FadeBehindPopup(&aFadedComponent, &aPopup, aFade);
        }
    }
