/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  pfGameGUIMgr                                                            //
//                                                                          //
//// History /////////////////////////////////////////////////////////////////
//                                                                          //
//  11.13.2001 mcn  - Created                                               //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include <stdlib.h>
#include "hsTimer.h"
#include "hsTypes.h"
#include "pfGameGUIMgr.h"
#include "pfGUIDialogMod.h"
#include "pfGUIDialogHandlers.h"
#include "pfGUIDialogNotifyProc.h"
#include "pfGUIControlMod.h"
#include "pfGUIPopUpMenu.h"

#include "pfMessage/pfGameGUIMsg.h"
#include "plMessage/plInputEventMsg.h"
#include "plMessage/plInputIfaceMgrMsg.h"
#include "pnMessage/plClientMsg.h"
#include "pnNetCommon/plSynchedObject.h"
#include "plInputCore/plInputInterface.h"
#include "plInputCore/plInputDevice.h"
#include "plInputCore/plInputInterfaceMgr.h"
#include "pnInputCore/plKeyMap.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnSceneObject/plSceneObject.h"    // So we can get the target sceneNode of a dialog
#include "plMessage/plConsoleMsg.h"
#include "plgDispatch.h"

#include "plResMgr/plKeyFinder.h"

#include "pfGUITagDefs.h"


//////////////////////////////////////////////////////////////////////////////
//// pfGameUIInputInterface Definition ///////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

class pfGameUIInputInterface : public plInputInterface
{
    protected:
        pfGameGUIMgr    * const fGUIManager;

        UInt8   fModifiers;
        UInt8   fButtonState;
        hsBool  fHaveInterestingCursor;
        UInt32  fCurrentCursor;

        virtual hsBool  IHandleCtrlCmd( plCtrlCmd *cmd );
        virtual hsBool  IControlCodeEnabled( ControlEventCode code );

    public:

        pfGameUIInputInterface( pfGameGUIMgr * const mgr );

        virtual UInt32  GetPriorityLevel( void ) const { return kGUISystemPriority; }
        virtual hsBool  InterpretInputEvent( plInputEventMsg *pMsg );
        virtual UInt32  GetCurrentCursorID( void ) const;
        virtual hsScalar GetCurrentCursorOpacity( void ) const;
        virtual hsBool  HasInterestingCursorID( void ) const { return fHaveInterestingCursor; }
        virtual hsBool  SwitchInterpretOrder( void ) const { return true; }

        virtual void    RestoreDefaultKeyMappings( void )
        {
            if( fControlMap != nil )
            {
                fControlMap->UnmapAllBindings();
                fControlMap->BindKey( KEY_BACKSPACE, B_CONTROL_EXIT_GUI_MODE, plKeyMap::kFirstAlways );
                fControlMap->BindKey( KEY_ESCAPE, B_CONTROL_EXIT_GUI_MODE, plKeyMap::kSecondAlways );
            }
        }
};

//////////////////////////////////////////////////////////////////////////////
//// pfGameGUIMgr Functions //////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

pfGameGUIMgr    *pfGameGUIMgr::fInstance = nil;


//// Constructor & Destructor ////////////////////////////////////////////////

pfGameGUIMgr::pfGameGUIMgr()
{
    fActivated = false;
    fInputCtlIndex = 0;
    fActiveDialogs = nil;

    fInputConfig = nil;

    fInstance = this;
    
    fDefaultCursor = plInputInterface::kCursorUp;
    fCursorOpacity = 1.f;
    fAspectRatio = 0;
}

pfGameGUIMgr::~pfGameGUIMgr()
{
    int     i;

    // the GUIMgr is dead!
    fInstance = nil;

    for( i = 0; i < fDialogs.GetCount(); i++ )
        UnloadDialog( fDialogs[ i ] );

    for( i = 0; i < fDialogToSetKeyOf.GetCount(); i++ )
        delete fDialogToSetKeyOf[i];

    if( fActivated )
        IActivateGUI( false );

    delete fInputConfig;
}


//// Init ////////////////////////////////////////////////////////////////////

hsBool  pfGameGUIMgr::Init( void )
{
    return true;
}

//// Draw ////////////////////////////////////////////////////////////////////

void    pfGameGUIMgr::Draw( plPipeline *p )
{
}

//// MsgReceive //////////////////////////////////////////////////////////////

hsBool  pfGameGUIMgr::MsgReceive( plMessage* pMsg )
{
    pfGameGUIMsg    *guiMsg = pfGameGUIMsg::ConvertNoRef( pMsg );
    if( guiMsg != nil )
    {
        if( guiMsg->GetCommand() == pfGameGUIMsg::kLoadDialog )
            LoadDialog( guiMsg->GetString(), nil, guiMsg->GetAge() );
        else if( guiMsg->GetCommand() == pfGameGUIMsg::kShowDialog )
            IShowDialog( guiMsg->GetString() );
        else if( guiMsg->GetCommand() == pfGameGUIMsg::kHideDialog )
            IHideDialog( guiMsg->GetString() );

        return true;
    }

    plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( pMsg );
    if( refMsg != nil )
    {
        if( refMsg->fType == kDlgModRef )
        {
            if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest ) )
            {
                IAddDlgToList( refMsg->GetRef() );
            }
            else if( refMsg->GetContext() & plRefMsg::kOnReplace )
            {
                IRemoveDlgFromList( refMsg->GetOldRef() );
                IAddDlgToList( refMsg->GetRef() );
            }
            else if( refMsg->GetContext() & ( plRefMsg::kOnRemove | plRefMsg::kOnDestroy ) )
            {
                IRemoveDlgFromList( refMsg->GetRef() );
            }
        }
        return true;
    }

    return hsKeyedObject::MsgReceive( pMsg );
}

//// IAddDlgToList ///////////////////////////////////////////////////////////

void    pfGameGUIMgr::IAddDlgToList( hsKeyedObject *obj )
{
    int     i;


    if( fDialogs.Find( (pfGUIDialogMod *)obj ) == fDialogs.kMissingIndex )
    {
        pfGUIDialogMod  *mod = pfGUIDialogMod::ConvertNoRef( obj );
        if( mod != nil )
        {
            mod->UpdateAspectRatio();   // adding a new dialog, make sure the correct aspect ratio is set
            fDialogs.Append( mod );


            // check to see if it is the dialog we are waiting for to be loaded
            for ( i=0 ; i<fDialogToSetKeyOf.Count() ; i++ )
            {
                if ( hsStrEQ(fDialogToSetKeyOf[i]->GetName(), mod->GetName()) )
                {
                    SetDialogToNotify(mod,fDialogToSetKeyOf[i]->GetKey());
                    // now remove this entry... we did it
                    delete fDialogToSetKeyOf[i];
                    fDialogToSetKeyOf.Remove(i);
                    // that's all the damage we can do for now...
                    break;
                }
            }
        }
    }

/*  // It's now officially "loaded"; take it off the pending list
    for( i = 0; i < fDlgsPendingLoad.GetCount(); i++ )
    {
        if( stricmp( fDlgsPendingLoad[ i ]->GetName(), ( (pfGUIDialogMod *)obj )->GetName() ) == 0 )
        {
            // Here it is
            delete fDlgsPendingLoad[ i ];
            fDlgsPendingLoad.Remove( i );
            break;
        }
    }
*/
}

//// IRemoveDlgFromList //////////////////////////////////////////////////////

void    pfGameGUIMgr::IRemoveDlgFromList( hsKeyedObject *obj )
{
    int idx = fDialogs.Find( (pfGUIDialogMod *)obj );
    if( idx != fDialogs.kMissingIndex )
    {
        pfGUIDialogMod  *mod = pfGUIDialogMod::ConvertNoRef( obj );
        hsAssert( mod != nil, "Non-dialog sent to gameGUIMgr::IRemoveDlg()" );

        if( mod != nil )
        {
            if( mod->IsEnabled() )
            {
                mod->SetEnabled( false );

                mod->Unlink();
                if( fActiveDialogs == nil )
                    IActivateGUI( false );
            }

            // Needed?
//              GetKey()->Release( mod->GetKey() );
            fDialogs.Remove( idx );
        }
    }

    // It's now officially "unloaded"; take it off the pending list
/*  int i;
    for( i = 0; i < fDlgsPendingUnload.GetCount(); i++ )
    {
        if( stricmp( fDlgsPendingUnload[ i ]->GetName(), ( (pfGUIDialogMod *)obj )->GetName() ) == 0 )
        {
            // Here it is
            delete fDlgsPendingUnload[ i ];
            fDlgsPendingUnload.Remove( i );
            break;
        }
    }
*/
}

//// LoadDialog //////////////////////////////////////////////////////////////

void    pfGameGUIMgr::LoadDialog( const char *name, plKey recvrKey, const char *ageName )
{
    // see if they want to set the receiver key once the dialog is loaded
    if ( recvrKey != nil )
    {
        // first see if we are loading a dialog that is already being loaded
        bool alreadyLoaded = false;
        int i;
        for ( i=0 ; i<fDialogToSetKeyOf.Count() ; i++ )
        {
            if ( hsStrEQ(fDialogToSetKeyOf[i]->GetName(), name) )
            {
                alreadyLoaded = true;
                break;
            }
        }
        if (!alreadyLoaded)
        {
            pfDialogNameSetKey* pDNSK = TRACKED_NEW pfDialogNameSetKey(name,recvrKey);
            fDialogToSetKeyOf.Append(pDNSK);
        }
    }

    hsStatusMessageF("\nLoading Dialog %s %s ... %f\n", name, ( ageName != nil ) ? ageName : "GUI", hsTimer::GetSeconds() );

    plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY );

    plClientMsg *msg = TRACKED_NEW plClientMsg( plClientMsg::kLoadRoomHold );
    msg->AddReceiver( clientKey );
    msg->AddRoomLoc(plKeyFinder::Instance().FindLocation(ageName ? ageName : "GUI", name));
    msg->Send();

    // Now add this dialog to a list of pending loads (will remove it once it's fully loaded)
//  fDlgsPendingLoad.Append( TRACKED_NEW pfDialogNameSetKey( name, nil ) );
}

//// IShowDialog /////////////////////////////////////////////////////////////

void    pfGameGUIMgr::IShowDialog( const char *name )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
        {
            ShowDialog( fDialogs[ i ] );
            fDialogs[i]->RefreshAllControls();
            break;
        }
    }
}

//// IHideDialog /////////////////////////////////////////////////////////////

void    pfGameGUIMgr::IHideDialog( const char *name )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
        {
            HideDialog( fDialogs[ i ] );
            break;
        }
    }
}

//// ShowDialog //////////////////////////////////////////////////////////////

void    pfGameGUIMgr::ShowDialog( pfGUIDialogMod *dlg, bool resetClickables /* = true */ )
{
    if ( resetClickables )
        plInputInterfaceMgr::GetInstance()->ResetClickableState();
    if( !dlg->IsEnabled() )
    {
        dlg->SetEnabled( true );

        // Add to active list
        if( fActiveDialogs == nil )
            IActivateGUI( true );
        
        dlg->LinkToList( &fActiveDialogs );

        return;
    }
}

//// HideDialog //////////////////////////////////////////////////////////////

void    pfGameGUIMgr::HideDialog( pfGUIDialogMod *dlg )
{
    if( dlg->IsEnabled() )
    {
        dlg->SetEnabled( false );

        dlg->Unlink();
        if( fActiveDialogs == nil )
            IActivateGUI( false );
    }
}

//// UnloadDialog ////////////////////////////////////////////////////////////
//  Destroy the dialog and all the things associated with it. Fun fun.

void    pfGameGUIMgr::UnloadDialog( const char *name )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
        {
            UnloadDialog( fDialogs[ i ] );
            break;
        }
    }
}

void    pfGameGUIMgr::UnloadDialog( pfGUIDialogMod *dlg )
{
//  IRemoveDlgFromList( dlg );

    // Add the name to our list of dialogs pending unload
//  fDlgsPendingUnload.Append( TRACKED_NEW pfDialogNameSetKey( dlg->GetName(), nil ) );

    plKey       sceneNodeKey = dlg->GetSceneNodeKey();
    if( sceneNodeKey == nil )
    {
        hsStatusMessageF( "Warning: Unable to grab sceneNodeKey to unload dialog %s; searching for it...", dlg->GetName() );
        sceneNodeKey = plKeyFinder::Instance().FindSceneNodeKey( dlg->GetKey()->GetUoid().GetLocation() );
    }

//  if( dlg->GetTarget() )
    if( sceneNodeKey != nil )
    {
        plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY );

        plClientMsg *msg = TRACKED_NEW plClientMsg( plClientMsg::kUnloadRoom );
        msg->AddReceiver( clientKey );
//      msg->SetProgressBarSuppression( true );
        msg->AddRoomLoc(sceneNodeKey->GetUoid().GetLocation());
        msg->Send();
    }
//  GetKey()->Release( dlg->GetKey() );
}

//// IsDialogLoaded ////// see if the dialog is in our list of loaded dialogs

hsBool  pfGameGUIMgr::IsDialogLoaded( const char *name )
{
    // search through all the dialogs we've loaded
    int     i;
    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
        {
            // found 'em
            return true;
        }
    }
    // nota
    return false;
}

pfGUIPopUpMenu  *pfGameGUIMgr::FindPopUpMenu( const char *name )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        pfGUIPopUpMenu *menu = pfGUIPopUpMenu::ConvertNoRef( fDialogs[ i ] );
        if( menu != nil && stricmp( menu->GetName(), name ) == 0 )
            return menu;
    }

    return nil;
}

std::vector<plPostEffectMod*> pfGameGUIMgr::GetDlgRenderMods( void ) const
{
    std::vector<plPostEffectMod*> retVal;
    pfGUIDialogMod* curDialog = fActiveDialogs;
    while (curDialog)
    {
        retVal.push_back(curDialog->GetRenderMod());
        curDialog = curDialog->GetNext();
    }
    return retVal;
}

///// SetDialogToNotify     - based on name
// This will Set the handler to a Notify Handler that will send a GUINotifyMsg to the receiver
//
void pfGameGUIMgr::SetDialogToNotify(const char *name, plKey recvrKey)
{
    int     i;
    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
        {
            SetDialogToNotify( fDialogs[ i ], recvrKey );
            break;
        }
    }
}

///// SetDialogToNotify     - pfGUIDialogMod*
// This will Set the handler to a Notify Handler that will send a GUINotifyMsg to the receiver
//
void pfGameGUIMgr::SetDialogToNotify(pfGUIDialogMod *dlg, plKey recvrKey)
{
    pfGUIDialogNotifyProc* handler = TRACKED_NEW pfGUIDialogNotifyProc(recvrKey);
    dlg->SetHandler(handler);
    handler->OnInit();
}


//// IActivateGUI ////////////////////////////////////////////////////////////
//  "Activates" the GUI manager. This means enabling the drawing of GUI
//  elements on the screen as well as rerouting input to us.

void    pfGameGUIMgr::IActivateGUI( hsBool activate )
{
    if( fActivated == activate )
        return;

    if( activate )
    {
        fInputConfig = TRACKED_NEW pfGameUIInputInterface( this );
        plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kAddInterface );
        msg->SetIFace( fInputConfig );
        plgDispatch::MsgSend( msg );
    }
    else
    {
        plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kRemoveInterface );
        msg->SetIFace( fInputConfig );
        plgDispatch::MsgSend( msg );

        hsRefCnt_SafeUnRef( fInputConfig );
        fInputConfig = nil;
    }

    fActivated = activate;
}

//// IHandleMouse ////////////////////////////////////////////////////////////
//  Distributes mouse events to the dialogs currently active

hsBool  pfGameGUIMgr::IHandleMouse( EventType event, hsScalar mouseX, hsScalar mouseY, UInt8 modifiers, UInt32 *desiredCursor ) 
{
    pfGUIDialogMod  *dlg;


    // Update interesting things first, no matter what, for ALL dialogs
    hsBool  modalPresent = false;
    for( dlg = fActiveDialogs; dlg != nil; dlg = dlg->GetNext() )
    {
        dlg->UpdateInterestingThings( mouseX, mouseY, modifiers, modalPresent );
        if (dlg->HasFlag( pfGUIDialogMod::kModal ))
            modalPresent = true;
    }

    for( dlg = fActiveDialogs; dlg != nil; dlg = dlg->GetNext() )
    {
        if( dlg->HandleMouseEvent( event, mouseX, mouseY, modifiers ) ||
            ( dlg->HasFlag( pfGUIDialogMod::kModal ) && event != pfGameGUIMgr::kMouseUp ) )
        {
            // If this dialog handled it, also get the cursor it wants
            *desiredCursor = dlg->GetDesiredCursor();
            return true;
        }
    }

    return false;
}

//// IHandleKeyEvt ///////////////////////////////////////////////////////////
//  Distributes mouse events to the dialogs currently active

hsBool  pfGameGUIMgr::IHandleKeyEvt( EventType event, plKeyDef key, UInt8 modifiers ) 
{
    pfGUIDialogMod  *dlg;


    for( dlg = fActiveDialogs; dlg != nil; dlg = dlg->GetNext() )
    {
        if( dlg->HandleKeyEvent( event, key, modifiers ) )
            return true;
    }

    return false;
}

//// IHandleKeyPress /////////////////////////////////////////////////////////
//  Like IHandleKeyPress, but takes in a char for distributing actual 
//  characters typed.

hsBool  pfGameGUIMgr::IHandleKeyPress( char key, UInt8 modifiers ) 
{
    pfGUIDialogMod  *dlg;


    for( dlg = fActiveDialogs; dlg != nil; dlg = dlg->GetNext() )
    {
        if( dlg->HandleKeyPress( key, modifiers ) )
            return true;
    }

    return false;
}

//// IModalBlocking //////////////////////////////////////////////////////////
//  Looks at the chain of active dialogs and determines if there's any modals
//  blocking input. Returns true if so.

hsBool  pfGameGUIMgr::IModalBlocking( void )
{
    return ( IGetTopModal() != nil ) ? true : false;
}

//// IGetTopModal ////////////////////////////////////////////////////////////
//  Returns the topmost (visible) modal dialog, nil if none.

pfGUIDialogMod  *pfGameGUIMgr::IGetTopModal( void ) const
{
    pfGUIDialogMod  *dlg;


    for( dlg = fActiveDialogs; dlg != nil; dlg = dlg->GetNext() )
    {
        if( dlg->HasFlag( pfGUIDialogMod::kModal ) )
            return dlg;
    }

    return nil;
}


//////////////////////////////////////////////////////////////////////////////
//// Control Config Class ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

pfGameUIInputInterface::pfGameUIInputInterface( pfGameGUIMgr * const mgr ) : plInputInterface(), fGUIManager( mgr )
{
    fModifiers = pfGameGUIMgr::kNoModifiers;
    fButtonState = 0;
    fHaveInterestingCursor = false;
    SetEnabled( true );         // Always enabled
    fCurrentCursor = kCursorUp;

    // Add our control codes to our control map. Do NOT add the key bindings yet.
    // Note: HERE is where you specify the actions for each command, i.e. net propagate and so forth.
    // This part basically declares us master of the bindings for these commands.
    
    // IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
    // RestoreDefaultKeyMappings()!!!!

    fControlMap->AddCode( B_CONTROL_EXIT_GUI_MODE, kControlFlagNormal | kControlFlagNoRepeat );

    // IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
    // RestoreDefaultKeyMappings()!!!!
}

hsBool  pfGameUIInputInterface::IControlCodeEnabled( ControlEventCode code )
{
    if( code == B_CONTROL_EXIT_GUI_MODE )
    {
        // Disable the exitGUIMode key binding if we don't have a modal dialog up or if 
        // the cursor is inside an edit or multiline edit control
        if( !fGUIManager->IModalBlocking() )
            return false;

        pfGUIDialogMod *dlg = fGUIManager->IGetTopModal();
        if( dlg != nil )
        {
            pfGUIControlMod *ctrl = dlg->GetFocus();
            if( ctrl != nil && ctrl->HasFlag( pfGUIControlMod::kTakesSpecialKeys ) )
                return false;
        }
    }
    return true;    // Enable all other codes
}

hsBool  pfGameUIInputInterface::IHandleCtrlCmd( plCtrlCmd *cmd )
{
    if( cmd->fControlCode == B_CONTROL_EXIT_GUI_MODE )
    {
        if( cmd->fControlActivated )
        {
            pfGUIDialogMod *dlg = fGUIManager->IGetTopModal();
            if( dlg != nil && dlg->GetHandler() != nil )
                dlg->GetHandler()->OnControlEvent( pfGUIDialogProc::kExitMode );
        }

        return true;
    }
    return false;
}

hsBool  pfGameUIInputInterface::InterpretInputEvent( plInputEventMsg *pMsg )
{
    hsBool      handled = false;


    /// The in-game UI has to do far more complicated control handling, so we just overload this entirely
    plKeyEventMsg *pKeyMsg = plKeyEventMsg::ConvertNoRef( pMsg );
    if( pKeyMsg )
    {
        // By default, we don't want the modifier keys treated as "handled", 'cause
        // we want the other interfaces to get them as well (unless we have a modal
        // as the top dialog).
        if( pKeyMsg->GetKeyCode() == KEY_SHIFT )
        {
            if( pKeyMsg->GetKeyDown() )
                fModifiers |= pfGameGUIMgr::kShiftDown;
            else
                fModifiers &= ~pfGameGUIMgr::kShiftDown;
        }
        else if( pKeyMsg->GetKeyCode() == KEY_CTRL )
        {
            if( pKeyMsg->GetKeyDown() )
                fModifiers |= pfGameGUIMgr::kCtrlDown;
            else
                fModifiers &= ~pfGameGUIMgr::kCtrlDown;
        }
        else if( pKeyMsg->GetKeyCode() == KEY_CAPSLOCK )
        {
            if( pKeyMsg->GetKeyDown() )
                fModifiers |= pfGameGUIMgr::kCapsDown;
            else
                fModifiers &= ~pfGameGUIMgr::kCapsDown;
        }
        else
        {
            // Sometimes I can't explain why Mathew does some of the things he does.
            // I going to replace his modifier flags (which I don't know why he thought he had to have his own)
            //   with the ones that are in the keymsg since they seem to be more accurate!
            fModifiers = 0;
            if ( pKeyMsg->GetShiftKeyDown() )
                fModifiers |= pfGameGUIMgr::kShiftDown;
            if ( pKeyMsg->GetCtrlKeyDown() )
                fModifiers |= pfGameGUIMgr::kCtrlDown;
            if ( pKeyMsg->GetCapsLockKeyDown() )
                fModifiers |= pfGameGUIMgr::kCapsDown;
            if( pKeyMsg->GetKeyDown() )
            {
                if( !pKeyMsg->GetRepeat() )
                    handled = fGUIManager->IHandleKeyEvt( pfGameGUIMgr::kKeyDown, pKeyMsg->GetKeyCode(), fModifiers );
                else
                    handled = fGUIManager->IHandleKeyEvt( pfGameGUIMgr::kKeyRepeat, pKeyMsg->GetKeyCode(), fModifiers );

                handled |= fGUIManager->IHandleKeyPress( plKeyboardDevice::KeyEventToChar( pKeyMsg ), fModifiers );
            }
            else
                handled = fGUIManager->IHandleKeyEvt( pfGameGUIMgr::kKeyUp, pKeyMsg->GetKeyCode(), fModifiers );
        }

        // We need to do early interception of a screenshot request, since they want
        // us to be able to take screen shots while in a modal GUI... whee
        // Also, this should only be run if the dialog didn't handle the command in
        // the first place (taking screenshots while the user is typing would be
        // awkward) and we must do it on key down because the key binding routines
        // also trigger on key-down and we don't want to be taking screen shots when
        // the user re-binds the screenshot command.
        // HACK HACK HACK
        if ((!handled) && (pKeyMsg->GetKeyDown()))
        {
            const plKeyBinding* keymap = plInputInterfaceMgr::GetInstance()->FindBindingByConsoleCmd("Game.KITakePicture");
            if (keymap)
            {
                unsigned keyFlags = 0;
                if (pKeyMsg->GetCtrlKeyDown())
                    keyFlags |= plKeyCombo::kCtrl;
                if (pKeyMsg->GetShiftKeyDown())
                    keyFlags |= plKeyCombo::kShift;
                plKeyCombo combo(pKeyMsg->GetKeyCode(), keyFlags);
                if ((keymap->GetKey1().IsSatisfiedBy(combo)) || (keymap->GetKey2().IsSatisfiedBy(combo)))
                {
                    // tell the KI to take the shot
                    plConsoleMsg * consoleMsg = NEWZERO(plConsoleMsg);
                    consoleMsg->SetCmd(plConsoleMsg::kExecuteLine);
                    consoleMsg->SetString("Game.KITakePicture");
                    consoleMsg->Send(nil, true);
                }
            }
        }

        bool modal = fGUIManager->IModalBlocking();
        return handled || modal; // we "handle" it if we are modal, even if it didn't do anything
    }

    plMouseEventMsg *pMouseMsg = plMouseEventMsg::ConvertNoRef( pMsg );
    if( pMouseMsg && fManager->IsClickEnabled() )
    {
        if( pMouseMsg->GetButton() == kLeftButtonDown )
        {
            handled = fGUIManager->IHandleMouse( pfGameGUIMgr::kMouseDown, pMouseMsg->GetXPos(), pMouseMsg->GetYPos(), fModifiers, &fCurrentCursor );
            if (handled)
                fButtonState |= kLeftButtonDown;
        }
        else if( pMouseMsg->GetButton() == kLeftButtonUp )
        {
            handled = fGUIManager->IHandleMouse( pfGameGUIMgr::kMouseUp, pMouseMsg->GetXPos(), pMouseMsg->GetYPos(), fModifiers, &fCurrentCursor );
            if ((handled) || (fButtonState & kLeftButtonDown)) // even if we didn't handle the mouse up, if we think the button is still down, we should clear our flag
                fButtonState &= ~kLeftButtonDown;
        }
        else if( pMouseMsg->GetButton() == kLeftButtonDblClk )
            handled = fGUIManager->IHandleMouse( pfGameGUIMgr::kMouseDblClick, pMouseMsg->GetXPos(), pMouseMsg->GetYPos(), fModifiers, &fCurrentCursor );
        else if( fButtonState & kLeftButtonDown )
            handled = fGUIManager->IHandleMouse( pfGameGUIMgr::kMouseDrag, pMouseMsg->GetXPos(), pMouseMsg->GetYPos(), fModifiers, &fCurrentCursor );
        else
            handled = fGUIManager->IHandleMouse( pfGameGUIMgr::kMouseMove, pMouseMsg->GetXPos(), pMouseMsg->GetYPos(), fModifiers, &fCurrentCursor );

        fHaveInterestingCursor = handled;
        return handled;
    }

    return false;
}   

UInt32  pfGameUIInputInterface::GetCurrentCursorID( void ) const
{
    if( fCurrentCursor == 0 )
    {
        if ( pfGameGUIMgr::GetInstance() )
            return pfGameGUIMgr::GetInstance()->GetDefaultCursor();
        else
            return kCursorUp;
    }

    return fCurrentCursor;
}

hsScalar pfGameUIInputInterface::GetCurrentCursorOpacity( void ) const
{
    if ( pfGameGUIMgr::GetInstance() )
        return pfGameGUIMgr::GetInstance()->GetCursorOpacity();
    else
        return 1.f;
}

//////////////////////////////////////////////////////////////////////////////
//// Tag Stuff ///////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

extern pfGUITag gGUITags[];     // From pfGUITagDefs.cpp

//// GetDialogFromTag ////////////////////////////////////////////////////////

pfGUIDialogMod  *pfGameGUIMgr::GetDialogFromTag( UInt32 tagID )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( fDialogs[ i ]->GetTagID() == tagID )
            return fDialogs[ i ];
    }

    return nil;
}

//// GetDialogFromString ////////////////////////////////////////////////////////

pfGUIDialogMod  *pfGameGUIMgr::GetDialogFromString( const char *name )
{
    int     i;


    for( i = 0; i < fDialogs.GetCount(); i++ )
    {
        if( stricmp( fDialogs[ i ]->GetName(), name ) == 0 )
            return fDialogs[ i ];
    }

    return nil;
}

//// GetControlFromTag ///////////////////////////////////////////////////////

pfGUIControlMod *pfGameGUIMgr::GetControlFromTag( pfGUIDialogMod *dlg, UInt32 tagID )
{
    return dlg->GetControlFromTag( tagID );
}

//// GetNumTags //////////////////////////////////////////////////////////////

UInt32          pfGameGUIMgr::GetNumTags( void )
{
    UInt32      count;


    for( count = 0; gGUITags[ count ].fID != 0; count++ );
    return count;   
}

//// GetTag //////////////////////////////////////////////////////////////////

pfGUITag        *pfGameGUIMgr::GetTag( UInt32 tagIndex )
{
    UInt32      count;

    
    for( count = 0; gGUITags[ count ].fID != 0; count++ );
    hsAssert( tagIndex < count, "Bad index to GetTag()" );
            
    return &gGUITags[ tagIndex ];
}

UInt32      pfGameGUIMgr::GetHighestTag( void )
{
    UInt32  i, id = 1;


    for( i = 0; gGUITags[ i ].fID != 0; i++ )
    {
        if( id < gGUITags[ i ].fID )
            id = gGUITags[ i ].fID;
    }

    return id;
}


void pfGameGUIMgr::SetAspectRatio(hsScalar aspectratio)
{
    hsScalar oldAspectRatio = fAspectRatio;

    // don't allow the aspectratio below 4:3
    hsScalar fourThree = 4.0f/3.0f;
    fAspectRatio = aspectratio < fourThree ? fourThree : aspectratio;

    if (fAspectRatio != oldAspectRatio)
    {
        // need to tell dialogs to update
        int i;
        for (i = 0; i < fDialogs.GetCount(); i++)
        {
            if (fDialogs[i])
                fDialogs[i]->UpdateAspectRatio();
        }
    }
}