/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 "HeadSpin.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_t   fModifiers;
        uint8_t   fButtonState;
        hsBool  fHaveInterestingCursor;
        uint32_t  fCurrentCursor;

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

    public:

        pfGameUIInputInterface( pfGameGUIMgr * const mgr );

        virtual uint32_t  GetPriorityLevel( void ) const { return kGUISystemPriority; }
        virtual hsBool  InterpretInputEvent( plInputEventMsg *pMsg );
        virtual uint32_t  GetCurrentCursorID( void ) const;
        virtual float 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 = 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 = 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( 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( 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 = 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 = 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 = new pfGameUIInputInterface( this );
        plInputIfaceMgrMsg *msg = new plInputIfaceMgrMsg( plInputIfaceMgrMsg::kAddInterface );
        msg->SetIFace( fInputConfig );
        plgDispatch::MsgSend( msg );
    }
    else
    {
        plInputIfaceMgrMsg *msg = 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, float mouseX, float mouseY, uint8_t modifiers, uint32_t *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_t 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( wchar_t key, uint8_t modifiers ) 
{
    pfGUIDialogMod  *dlg;

    // Really... Don't handle any nil keypresses
    if (key == nil)
        return false;

    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 );

                if (pKeyMsg->GetKeyChar())
                    handled |= fGUIManager->IHandleKeyPress( pKeyMsg->GetKeyChar(), 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()) && !pKeyMsg->GetKeyChar())
        {
            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_t  pfGameUIInputInterface::GetCurrentCursorID( void ) const
{
    if( fCurrentCursor == 0 )
    {
        if ( pfGameGUIMgr::GetInstance() )
            return pfGameGUIMgr::GetInstance()->GetDefaultCursor();
        else
            return kCursorUp;
    }

    return fCurrentCursor;
}

float 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_t 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_t tagID )
{
    return dlg->GetControlFromTag( tagID );
}

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

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


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

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

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

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

uint32_t      pfGameGUIMgr::GetHighestTag( void )
{
    uint32_t  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(float aspectratio)
{
    float oldAspectRatio = fAspectRatio;

    // don't allow the aspectratio below 4:3
    float 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();
        }
    }
}
