tsrc/consoleplayer/common/testappbase.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 00:36:08 +0300
changeset 37 36c0918edf90
parent 35 b0f0be18af85
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/*
 * Copyright (c) 2010 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:
 * Source file containing common test app functionality.
 * 
 */

#include <f32file.h>
#include <remconcoreapitarget.h>
#include <remconinterfaceselector.h>

#include "testappbase.h"

const TInt KLeftSoftKeyCode  = EKeyDevice0;
const TInt KRightSoftKeyCode = EKeyDevice1;

const TInt KHelpWindowBorderPixels = 20;
const TInt KHelpWindowSpaceBetweenColumns = 25;
const TInt KHelpWindowSpaceBetweenRows = 2;

struct TKeyListEntry
    {
    TInt         keyCode;
    const TText* keyName;
    };

const TKeyListEntry KSupportedKeys[KSupportedKeysCount] =
        {
            {EKeyEnter,      STR("Enter")},      
            {EKeyUpArrow,    STR("Up")},
            {EKeyDownArrow,  STR("Down")}, 
            {EKeyLeftArrow,  STR("Left")},
            {EKeyRightArrow, STR("Right")},
            {'0',            STR("0")},
            {'1',            STR("1")},  
            {'2',            STR("2")}, 
            {'3',            STR("3")},
            {'4',            STR("4")},
            {'5',            STR("5")},
            {'6',            STR("6")},
            {'7',            STR("7")},
            {'8',            STR("8")},
            {'9',            STR("9")},
            {'*',            STR("*")},
            {'#',            STR("#")}
        };
        
static TPtrC KeyName( TInt aIndex )
    {
    return TPtrC(KSupportedKeys[aIndex].keyName);
    }

// Portable app implementation

CTestAppBase::CTestAppBase( TInt aFontSize ) : 
    CActive( EPriorityStandard ),
    iFontSize( aFontSize ),
    iHelpSemitransparentBackgroundActive( true )
    {    
    CActiveScheduler::Add(this);
    }

CTestAppBase::~CTestAppBase()
    {
    delete iInterfaceSelector;
    delete iHelpWindow;
    delete iSelectionWindow;
    delete iGc;
    delete iWindowGroup;
    delete iScreenDevice;
    delete iTypefaceStore;
    iWs.Close();
    iFs.Close();
    iFileHistory.ResetAndDestroy();
    }

void CTestAppBase::BaseConstructL( const TOperationsPage* aKeyMap, TInt aPageCount )
    {
    iKeyMap = aKeyMap;
    iPageCount = aPageCount;
    
    User::LeaveIfError( iFs.Connect() );        
    
    User::LeaveIfError( iWs.Connect() );    

    iScreenDevice = new(ELeave) CWsScreenDevice( iWs );
    User::LeaveIfError( iScreenDevice->Construct() );
    iDisplaySize = iScreenDevice->SizeInPixels();
    
    User::LeaveIfError( iScreenDevice->CreateContext(iGc) );

    iWindowGroup = new(ELeave) RWindowGroup( iWs );
    User::LeaveIfError( iWindowGroup->Construct( KNullWsHandle ) );

    iSelectionWindow = new(ELeave) RWindow( iWs );
    User::LeaveIfError( iSelectionWindow->Construct( *iWindowGroup, KNullWsHandle ) );    
    iSelectionWindow->SetVisible(false);
    iSelectionWindow->Activate();    
            
    // Load the font to be used for all text operations.
    TFontSpec fontSpec;
    fontSpec.iHeight = iFontSize;
    iTypefaceStore = CFbsTypefaceStore::NewL(NULL);
    
    User::LeaveIfError( iTypefaceStore->GetNearestFontToDesignHeightInPixels(iFont, fontSpec) );

    CalculateHelpWindowSize();

    iHelpWindowTopRight = TPoint(iDisplaySize.iWidth/2 - iHelpWindowSize.iWidth/2, 
                                 iDisplaySize.iHeight/2 - iHelpWindowSize.iHeight/2);

    iHelpWindow = new(ELeave) RWindow( iWs );
    User::LeaveIfError( iHelpWindow->Construct( *iWindowGroup, KNullWsHandle ) );
    iHelpWindow->SetExtent( iHelpWindowTopRight, iHelpWindowSize );
    iHelpWindow->SetTransparencyAlphaChannel();
    iHelpWindow->SetBackgroundColor(KRgbTransparent);
    iHelpWindow->SetVisible(false);
    iHelpWindow->Activate();
    iHelpWindow->PointerFilter(EPointerFilterDrag, 0);

    // Initialize soft key indices
    for( TInt i = 0; i < aPageCount; i++ )
        {
        iSoftkeyIndices[i] = iKeyMap[i].defaultSoftkeyIndex;
        }
    
    // Only need to draw the help text when the options page is changed.  Window is displayed later by
    // toggling the visibility of the window.
    DrawHelpText();
    
    // THE FOLLOWING CODE IS COMMENTED OUT BECAUSE IT CAUSES A CRASH IN NCP BUILDS.
    // THIS CAN BE ENABLED IN DFS BUILDS, TO ALLOW FOR TWO BUTTON OPERATION USING THE VOLUME KEYS.
    //
    // Since some phone have no keyboard or soft keys, treat the volume keys like the soft keys.
    // SetupVolumeKeysL();    
    }

void CTestAppBase::SetupVolumeKeysL()
    {
    iInterfaceSelector = CRemConInterfaceSelector::NewL();
    iCoreTarget = CRemConCoreApiTarget::NewL(*iInterfaceSelector, *this);
    iInterfaceSelector->OpenTargetL();
    }

void CTestAppBase::StartMonitoringWindowEvents()
    {
    // Request notification for windows server events, to detect key presses.    
    SetActive();
    iWs.EventReady( &iStatus );
    }

TInt CTestAppBase::CurrentPageNumber()
    {
    return iCurrentPage+1;
    }

TPtrC CTestAppBase::CurrentPageName()
    {
    return TPtrC(iKeyMap[iCurrentPage].pageName);
    }

TPtrC CTestAppBase::CurrentSoftkeyName()
    {
    return TPtrC(iKeyMap[iCurrentPage].mapping[iSoftkeyIndices[iCurrentPage]].text);    
    }
    
TPtrC CTestAppBase::KeyMapText( TInt aIndex, TInt aPage )
    {
    return TPtrC(iKeyMap[aPage].mapping[aIndex].text);
    }

TInt CTestAppBase::KeyMapOperation(TInt aIndex, TInt aPage )
    {
    return iKeyMap[aPage].mapping[aIndex].operation;
    }

void CTestAppBase::IncrementKeymapIndex( TInt& aIndex, TInt aPage )
    {
    aIndex = (aIndex + 1) % KSupportedKeysCount;
    while( iKeyMap[aPage].mapping[aIndex].operation == KOperation_None )
        {
        aIndex = (aIndex + 1) % KSupportedKeysCount;
        }
    }

void CTestAppBase::DecrementKeymapIndex( TInt& aIndex, TInt aPage )
    {
    aIndex = (aIndex + KSupportedKeysCount - 1) % KSupportedKeysCount;
    while( iKeyMap[aPage].mapping[aIndex].operation == KOperation_None )
        {
        aIndex = (aIndex + KSupportedKeysCount - 1) % KSupportedKeysCount;
        }
    }

void CTestAppBase::CalculateHelpWindowSize()
    {
    iHelpWindowColumn1Width = 0;
    iHelpWindowColumn2Width = 0;
    
    // Find the widest strings for each column to determine the width of the window.
    for( TInt index = 0; index < KSupportedKeysCount; index++ )
        {
        TInt width = iFont->TextWidthInPixels(KeyName(index));
        if( width > iHelpWindowColumn1Width )
            {
            iHelpWindowColumn1Width = width;
            }
        
        for( TInt index2 = 0; index2 < iPageCount; index2++ )
            {
            width = iFont->TextWidthInPixels(KeyMapText(index,index2));
            if( width > iHelpWindowColumn2Width )
                {
                iHelpWindowColumn2Width = width;
                }                    
            }
        }    
    
    iHelpWindowSize.iWidth = 2*KHelpWindowBorderPixels + iHelpWindowColumn1Width + 
                             KHelpWindowSpaceBetweenColumns + iHelpWindowColumn2Width;
    
    iHelpWindowSize.iHeight = 2*KHelpWindowBorderPixels + iFontSize*KSupportedKeysCount + 
                              KHelpWindowSpaceBetweenRows*(KSupportedKeysCount-1);
    }

void CTestAppBase::StartReceivingPointerEvents()
    {
    iRoutePointerEventsToApp = true;
    }

void CTestAppBase::StopReceivingPointerEvents()
    {
    iRoutePointerEventsToApp = false;
    }

//virtual void HandlePointerEvent( const TPointerEvent& aEvent ) {}

CTestAppBase::TTestAppPointerEvent CTestAppBase::CharacterizePointerEvent( const TAdvancedPointerEvent& event )
    {
    TTestAppPointerEvent returnValue = EPointerEvent_None;
    
    RDebug::Printf( "POINTER EVENT:" );        
    RDebug::Printf( "iType=%i", event.iType );
    RDebug::Printf( "iModifiers=%x", event.iModifiers );
    RDebug::Printf( "iPosition=%i,%i", event.iPosition.iX, event.iPosition.iY );
    RDebug::Printf( "iParentPosition=%i,%i", event.iParentPosition.iX, event.iParentPosition.iY );
    RDebug::Printf( "PointerNumber=%i", event.PointerNumber() );
    RDebug::Printf( "Proximity=%i", event.Proximity() );
    RDebug::Printf( "Pressure=%i", event.Pressure() );
    RDebug::Printf( "ProximityAndPressure=%i", event.ProximityAndPressure() );
    RDebug::Printf( "Position3D=%i,%i,%i", event.Position3D().iX, event.Position3D().iY, event.Position3D().iZ );
    RDebug::Printf( "Pressure3D=%i,%i,%i", event.Pressure3D().iX, event.Pressure3D().iY, event.Pressure3D().iZ );
    RDebug::Printf( "PositionAndPressure3D=%i,%i,%i", event.PositionAndPressure3D().iX, event.PositionAndPressure3D().iY, event.PositionAndPressure3D().iZ );
    
    switch( event.iType )
        {
        case TPointerEvent::EButton1Down:
            {
            iPointerDownPosition = event.iPosition;            
            break;
            }
        case TPointerEvent::EButton1Up:
            {
            TInt xDelta = event.iPosition.iX - iPointerDownPosition.iX; 
            TInt yDelta = event.iPosition.iY - iPointerDownPosition.iY;
            
            TInt xMagnitude = xDelta;
            if( xMagnitude < 0 )
                {
                xMagnitude = -xMagnitude;
                }
            
            TInt yMagnitude = yDelta;
            if( yMagnitude < 0 )
                {
                yMagnitude = -yMagnitude;
                }

            const TInt KTapThreshold = 30;
            
            if( yMagnitude > xMagnitude )
                {
                if( yMagnitude < KTapThreshold )
                    {
                    RDebug::Printf( "POINTER EVENT ENTER x=%i y=%i", xDelta, yDelta );                            
                    returnValue = EPointerEvent_Select;
                    }
                else if( yDelta < 0 )
                    {
                    RDebug::Printf( "POINTER EVENT UP x=%i y=%i", xDelta, yDelta );        
                    returnValue = EPointerEvent_Up;
                    }
                else
                    {
                    RDebug::Printf( "POINTER EVENT DOWN x=%i y=%i", xDelta, yDelta );                            
                    returnValue = EPointerEvent_Down;
                    }                
                }
            else
                {
                if( xMagnitude < KTapThreshold )
                    {
                    RDebug::Printf( "POINTER EVENT ENTER x=%i y=%i", xDelta, yDelta );                            
                    returnValue = EPointerEvent_Select;
                    }
                else if( xDelta < 0 )
                    {
                    RDebug::Printf( "POINTER EVENT LEFT x=%i y=%i", xDelta, yDelta );        
                    returnValue = EPointerEvent_Left;
                    }
                else
                    {
                    RDebug::Printf( "POINTER EVENT RIGHT x=%i y=%i", xDelta, yDelta );                            
                    returnValue = EPointerEvent_Right;
                    }                                
                }
            break;
            }
        }
    
    return returnValue;
    }

void CTestAppBase::RunL()
    {
    if( iWait.IsStarted() )
        {
        // This is an event during synchronous list selection.  Stop the nested scheduler.
        iWait.AsyncStop();
        return;
        }    
    
    TWsEvent event;
    iWs.GetEvent(event);

    TInt operationIndex = -1;
    
    TInt keyCode = 0;
    bool processKeyCode = false;
    
    TInt operation = KOperation_None; 
    
    // Other potentially useful events are EEventKeyUp and EEventKeyDown.
    
    if( event.Type() == EEventKey )
        {
        keyCode = event.Key()->iCode;
                   
        RDebug::Printf( "key event keyCode=%x/%c scanCode=%x/%c", keyCode, keyCode, event.Key()->iScanCode, event.Key()->iScanCode );
        
        // Allow subclasses a chance to consume the key event directly.  If that happens, then
        // do not handle the key as normal.
        if( !ConsumeKeyEvent( keyCode ) )
            {
            processKeyCode = true;        
            }
        }
    else if( event.Type() == EEventPointer )
        {
        TAdvancedPointerEvent* p = event.Pointer();

        if( iRoutePointerEventsToApp  )
            {
            HandlePointerEvent( *p );
            }
        else
            {        
            TTestAppPointerEvent pointerEvent = CharacterizePointerEvent( *p );

            switch( pointerEvent )
                {
                case EPointerEvent_None:
                    // Do nothing.
                    break;
                case EPointerEvent_Up:
                    operation = KOperation_PreviousOption;
                    break;
                case EPointerEvent_Down:
                    operation = KOperation_NextOption;
                    break;
                case EPointerEvent_Left:
                    operation = KOperation_PreviousOptionPage;
                    break;
                case EPointerEvent_Right:
                    operation = KOperation_NextOptionPage;
                    break;
                case EPointerEvent_Select:
                    operation = KOperation_ExecuteOption;
                    break;
                }
            }
        }
    else
        {
        RDebug::Printf( "other event, type=%i", event.Type() );    
        }
        
    if( processKeyCode )
        {
        // If one of the softkeys were pressed then take the appropriate action.
        // This is to support a two button touch device with no numeric keypad.
        // Support 'A' and 'B' also, for the NCP emulator where a keyboard  is
        // not displayed.
        switch( keyCode )
            {
            case KLeftSoftKeyCode:
            case 'a':
            case 'A':
                {
                operation = KOperation_NextOption;
                break;
                } 
            case KRightSoftKeyCode:
            case 'b':
            case 'B':
                {
                // Execute softkey function.
                operation = KOperation_ExecuteOption;
                break;
                }
            default:
                {
                // Search for keycode in keymap.  If not found then the key was not a valid
                // key, so ignore.                
                TInt index = 0;
                while( (index < KSupportedKeysCount) && (operationIndex == -1))
                    {
                    if( KSupportedKeys[index].keyCode == keyCode )
                        {                
                        // Found!
                        operationIndex = index;
                        }
                    else
                        {
                        index++;
                        }
                    }
                break;
                }                
            }        
        }
    
    if( operation == KOperation_ExecuteOption )
        {
        operationIndex = iSoftkeyIndices[iCurrentPage];
        }
    
    if( operationIndex >= 0 )
        {
        operation = KeyMapOperation(operationIndex, iCurrentPage);    
        }
    
    if( operation != KOperation_None )
        {
        // Valid operation.

        switch( operation )
            {
            case KOperation_Exit:
                {                
                CActiveScheduler::Stop();
                break;
                }
            case KOperation_PreviousOption:
                {
                // Change softkey function.
                DecrementKeymapIndex( iSoftkeyIndices[iCurrentPage], iCurrentPage );
                
                // Redraw help text, since a new function should now be underlined.
                DrawHelpText();
                
                // Notify subclass that softkey function has been updated.
                SoftkeyFunctionUpdated();
                break;
                }
            case KOperation_NextOption:
                {
                // Change softkey function.
                IncrementKeymapIndex( iSoftkeyIndices[iCurrentPage], iCurrentPage );
                
                // Redraw help text, since a new function should now be underlined.
                DrawHelpText();
                
                // Notify subclass that softkey function has been updated.
                SoftkeyFunctionUpdated();
                break;
                }
            case KOperation_PreviousOptionPage:
                {
                iCurrentPage = (iCurrentPage + iPageCount - 1) % iPageCount;
                DrawHelpText();
                SoftkeyFunctionUpdated();                
                break;
                }
            case KOperation_NextOptionPage:
                {
                iCurrentPage = (iCurrentPage + 1) % iPageCount;
                DrawHelpText();
                SoftkeyFunctionUpdated();                
                break;
                }
            case KOperation_ToggleHelpVisibility:    
                {
                // Toggle help text on/off.
                iHelpActive = !iHelpActive;
                iHelpWindow->SetVisible(iHelpActive);
                break;
                }
            case KOperation_ToggleHelpTransparency:
                {
                iHelpSemitransparentBackgroundActive = !iHelpSemitransparentBackgroundActive;
                if( iHelpSemitransparentBackgroundActive )
                    {
                    // Turn on help if it is currently off.
                    iHelpActive = true;
                    iHelpWindow->SetVisible(true);
                    }
                DrawHelpText();
                break;            
                }
            default:
                {
                // Pass operation to subclass.
                TPtrC operatioName( KeyMapText(operationIndex, iCurrentPage) );
                ExecuteOperation( operation, operatioName );                        
                break;
                }
            }    
        }        
    
    SetActive();
    iWs.EventReady( &iStatus );
    }            

void CTestAppBase::DoCancel()
    {
    iWs.EventReadyCancel();
    }

// TODO: ALLOW SUBCLASS TO SPECIFY COLOR SELECTIONS

TInt CTestAppBase::SelectFromListL( TPoint aTopLeft,
                                    TSize aSize,
                                    const TDesC& aHeaderText, 
                                    RPointerArray<TDesC>& aSelectionList, 
                                    TInt aInitialSelectionIndex )
    {
    iSelectionWindow->SetExtent( aTopLeft, aSize );
    iSelectionWindow->SetVisible( true );

    const TInt KRowIncrement = iFontSize + 2;
    
    TInt entriesPerPage = aSize.iHeight / KRowIncrement - 4;
    
    TInt returnValue = -2;
    TInt startIndex = 0;
    TInt selected = aInitialSelectionIndex;     
    
    while( returnValue == -2 )
        {
        iGc->Activate(*iSelectionWindow);
    
        iSelectionWindow->Invalidate();
        iSelectionWindow->BeginRedraw();
    
        iGc->Reset();
        
        iGc->UseFont(iFont);                
        iGc->SetBrushColor(KRgbDarkBlue);
        
        iGc->Clear();

        // KRgbWhite seems to be having problems (0xffffff) in some emulators,
        // but 0xfefefe is working, so use that instead of white.        
        iGc->SetPenColor(0xfefefe);

        const TInt KHeaderColumn = 5;
        const TInt KEntryColumn = 15;
        
        TInt row = KRowIncrement;

        iGc->SetUnderlineStyle(EUnderlineOff);

        iGc->DrawText( aHeaderText, TPoint(KHeaderColumn, row) );
        row += (KRowIncrement + 5);
        
        TBool again = true;
        TInt backIndex = -1;
        TInt forwardIndex = -1;
        TInt offset = 0;
                    
        while( again )
            {
            if( selected == offset )
                {
                iGc->SetUnderlineStyle(EUnderlineOn);
                }
            else
                {
                iGc->SetUnderlineStyle(EUnderlineOff);            
                }
        
            if( (offset < entriesPerPage) && (startIndex+offset < aSelectionList.Count()) )
                {
                iGc->DrawText( *aSelectionList[startIndex+offset], TPoint(KEntryColumn, row) );
                row += KRowIncrement;

                offset++;
                }
            else
                {
                again = false;
                if( startIndex + offset < aSelectionList.Count() )
                    {
                    iGc->DrawText( _L("<page down>"), TPoint(KEntryColumn, row) );
                    row += KRowIncrement;
                    
                    forwardIndex = offset;
                    offset++;
                    }
                if( startIndex > 0 )
                    {
                    if( selected == offset )
                        {
                        iGc->SetUnderlineStyle(EUnderlineOn);
                        }
                    else
                        {
                        iGc->SetUnderlineStyle(EUnderlineOff);            
                        }
                
                    iGc->DrawText( _L("<page up>"), TPoint(KEntryColumn, row) );
                    row += KRowIncrement;

                    backIndex = offset;
                    offset++;
                    }
                }
            }
        
        iSelectionWindow->EndRedraw();
       
        iGc->Deactivate();

        TInt keyCode = WaitForAnyKey();        
        
        switch( keyCode )
            {
            case EKeyUpArrow:
                if( selected == 0 )
                    {
                    selected = offset-1;
                    }
                else
                    {
                    selected -= 1;
                    }
                break;
                
            case EKeyDownArrow:
            case KLeftSoftKeyCode:                        
            case 'a':
            case 'A':
                selected += 1;
                if( selected == offset )
                    {
                    selected = 0;
                    }
                break;

            case EKeyLeftArrow:
                if( backIndex >= 0 )
                    {
                    startIndex -= entriesPerPage;
                    selected = 0;                        
                    }
                else
                    {
                    returnValue = -1;
                    again = false;
                    }
                break;
                
            case EKeyRightArrow:
                if( forwardIndex >= 0 )
                    {
                    startIndex += entriesPerPage;
                    selected = 0;                    
                    }
                break;

            case EKeyEnter:
            case KRightSoftKeyCode:                        
            case 'b':
            case 'B':
                if( selected == forwardIndex )
                    {
                    startIndex += entriesPerPage;
                    selected = 0;                    
                    }
                else if( selected == backIndex )
                    {
                    startIndex -= entriesPerPage;
                    selected = 0;
                    }
                else
                    {
                    returnValue = startIndex+selected;
                    }
                break;
            }
        }

    iSelectionWindow->SetVisible( false );
    
    return returnValue;        
    }

TInt CTestAppBase::SelectFromListL( TPoint aTopLeft,
                                    TSize aSize,
                                    const TDesC& aHeaderText, 
                                    RPointerArray<HBufC>& aSelectionList, 
                                    TInt aInitialSelectionIndex )
    {
    RPointerArray<TDesC> aSelectionList2;
    
    for( TInt index = 0; index < aSelectionList.Count(); index++ )
        {
        aSelectionList2.Append( aSelectionList[index]);
        }
    
    TInt returnValue = SelectFromListL( aTopLeft, aSize, aHeaderText, aSelectionList2, aInitialSelectionIndex );
    
    aSelectionList2.Close();
    
    return returnValue;
    }

void CTestAppBase::BuildDriveListL( RPointerArray<HBufC>& aDrives )
    {
    TDriveList driveList;
    TInt err = iFs.DriveList( driveList );
    
    for( TInt index = 0; index < driveList.Length(); index++ )
        {
        if( driveList[index] != 0 )
            {
            HBufC* drive = HBufC::NewL( 2 );
            drive->Des().Format( _L("%c:"), 'A' + index);
            aDrives.Append( drive );
            }
        }
    }

bool CTestAppBase::SelectDriveL( TPoint aTopLeft,
                                 TSize aWindowSize,
                                 const TDesC& aHeaderText, 
                                 TDes& aDrive )
    {
    RPointerArray<HBufC> drives;
    BuildDriveListL( drives );
    
    TInt index = SelectFromListL( aTopLeft, aWindowSize, aHeaderText, drives );
    
    bool returnValue = false;
    
    if( index >= 0 )
        {
        returnValue = true;
        aDrive.Copy( *(drives[index]) );
        aDrive.Append( _L("\\") );
        }
    
    drives.ResetAndDestroy();
    
    return returnValue;
    }

bool CTestAppBase::SelectFileL( TPoint aTopLeft,
                                TSize aWindowSize,
                                const TDesC& aHeaderText, 
                                const TDesC& aDrive, 
                                TDes& aFullFilename )
    {
    TFileName directory;
    
    DoSelectFileL( aTopLeft, aWindowSize, aHeaderText, aDrive, 0, directory, aFullFilename );
    
    aFullFilename.Insert( 0, directory );
    
    return aFullFilename.Length() > 0;
    }

bool CTestAppBase::SelectFileWithHistoryL( TPoint aTopLeft,
                                           TSize aSize,
                                           TDes& aFullFilename,
                                           const TDesC& aHistoryFilename,
                                           TInt aMaxHistoryEntries,
                                           const TDesC& aPrompt )
    {
    RPointerArray<HBufC> drives;
    BuildDriveListL( drives );
        
    RPointerArray<TDesC> selections;

    for( TInt index = 0; index < drives.Count(); index++ )
        {
        selections.Append( drives[index]);
        }
    TInt drivesBaseIndex = 0;
        
    // Add file history to the end of the drive list.  Newest files are last, so add in reverse order.
    // For convenience, add the last history entry at the top, before the drive list.
    ReadFileHistory( aHistoryFilename );
    if( iFileHistory.Count() > 0 )
        {
        selections.Insert( iFileHistory[iFileHistory.Count()-1], 0 );        
        drivesBaseIndex++;
        }
    for(TInt index = iFileHistory.Count()-2; index >= 0; index-- )
        {        
        selections.Append( iFileHistory[index]);
        }

    bool done = false;
    bool selected = true;
    
    while( !done )
        {
        TInt index = SelectFromListL( aTopLeft, aSize, aPrompt, selections );

        if( index < 0 )
            {
            selected = false;
            done = true;
            }        
        else if( (index >= drivesBaseIndex) && (index - drivesBaseIndex < drives.Count()) )
            {
            TBuf<10> drive;
            drive.Copy( *(selections[index]) );
            drive.Append( _L("\\") );
            
            done = SelectFileL( aTopLeft, aSize, aPrompt, drive, aFullFilename );        
            }
        else
            {    
            // Remove the selected file from the history, so that it will pop up to the top of the list
            // as the most recently selected file.
            TInt historyIndex;
            if( index == 0 )
                {
                historyIndex = iFileHistory.Count() - 1;
                }
            else
                {
                historyIndex = iFileHistory.Count() - index + drives.Count() - 1;
                }
            aFullFilename.Copy( *(selections[index]) );
            delete iFileHistory[historyIndex];
            iFileHistory.Remove( historyIndex );            

            done = true;
            }
        }
    
    if( selected )
        {
        AddToFileHistory( aFullFilename, aHistoryFilename, aMaxHistoryEntries );        
        }
    
    selections.Reset();
    drives.ResetAndDestroy();
    
    return selected;
    }

bool CTestAppBase::SelectIntegerL( TPoint aTopLeft,
                                   TSize aSize,
                                   const TDesC& aHeaderText,
                                   TInt aMin,
                                   TInt aMax,
                                   TInt& aSelection )
    {
    // currently no way to exit out of this selection
    
    iSelectionWindow->SetExtent( aTopLeft, aSize );
    iSelectionWindow->SetVisible( true );

    bool done = false;
    
    while( !done )
        {
        iGc->Activate(*iSelectionWindow);
    
        iSelectionWindow->Invalidate();
        iSelectionWindow->BeginRedraw();
    
        iGc->Reset();
        
        iGc->UseFont(iFont);                
        iGc->SetBrushColor(KRgbDarkBlue);
        
        iGc->Clear();

        // KRgbWhite seems to be having problems (0xffffff) in some emulators,
        // but 0xfefefe is working, so use that instead of white.        
        iGc->SetPenColor(0xfefefe);

        TBuf<120> buffer;
        buffer.Copy( aHeaderText );        
        buffer.AppendFormat( _L(" %i"), aSelection );
        
        iGc->DrawText( buffer, TPoint(5, iFontSize+2) );

        iSelectionWindow->EndRedraw();
       
        iGc->Deactivate();

        TInt keyCode = WaitForAnyKey();
        
        switch( keyCode )
            {
            case EKeyUpArrow:
                aSelection -= 10;
                break;
                
            case EKeyDownArrow:
            case KLeftSoftKeyCode:                        
            case 'a':
            case 'A':
                aSelection += 10;
                break;

            case EKeyLeftArrow:
                aSelection--;
                break;
                
            case EKeyRightArrow:
                aSelection++;
                break;

            case EKeyEnter:
            case KRightSoftKeyCode:                        
            case 'b':
            case 'B':
                done = true;
                break;
            }
        
        if( aSelection > aMax )
            {
            aSelection = aMin;
            }
        else if( aSelection < aMin )
            {
            aSelection = aMax;
            }        
        }

    iSelectionWindow->SetVisible( false );
    
    return true;
    }

TInt CTestAppBase::WaitForAnyKey()
    {
    TInt returnValue = 0;
    
    bool done = false;
    
    while( !done )
        {        
        // Have to use this tricky nested active scheduler technique to allow the active object
        // used to remap volume keys to run.
        SetActive();
        iWs.EventReady( &iStatus );
        iWait.Start();
    
        TWsEvent event;
        iWs.GetEvent(event);
    
        // Other potentially useful events are EEventKeyUp and EEventKeyDown.
        
        if( event.Type() == EEventKey )
            {
            done = true;
            returnValue = event.Key()->iCode;
            }
        else if( event.Type() == EEventPointer )
            {
            TAdvancedPointerEvent* p = event.Pointer();

            TTestAppPointerEvent pointerEvent = CharacterizePointerEvent( *p );

            switch( pointerEvent )
                {
                case EPointerEvent_None:
                    // Do nothing.
                    break;
                case EPointerEvent_Up:
                    returnValue = EKeyUpArrow;
                    done = true;
                    break;
                case EPointerEvent_Down:
                    returnValue = EKeyDownArrow;
                    done = true;
                    break;
                case EPointerEvent_Left:
                    returnValue = EKeyLeftArrow;
                    done = true;
                    break;
                case EPointerEvent_Right:
                    returnValue = EKeyRightArrow;
                    done = true;
                    break;
                case EPointerEvent_Select:
                    returnValue = EKeyEnter;
                    done = true;
                    break;
                }
            }        
        }
    
    return returnValue;
    }

TInt CTestAppBase::ReadFile( const TDesC& aFilename, RPointerArray<HBufC>& aContents )
    {
    RFile inputFile;
    TInt err = inputFile.Open(iFs, aFilename, EFileShareReadersOnly | EFileStream | EFileRead );
    
    if( err == KErrNone )
        {
        TInt fileSize;
        inputFile.Size( fileSize );
        
        RBuf8 contents;
        contents.Create( fileSize );

        inputFile.Read( contents, fileSize );

        inputFile.Close();

        TPtrC8 remaining( contents );
        
        while( remaining.Length() > 0 )
            {
            // Removing leading CR/LFs, if any.
            while( remaining.Length() > 0 && (remaining[0] == '\n' || remaining[0] == '\r') )
                {
                remaining.Set(remaining.Right(remaining.Length()-1));            
                }

            if( remaining.Length() == 0 )
                {
                break;
                }

            // Find next CR/LF, or end of string.
            TInt separatorIndex = 1;
            while( (separatorIndex < remaining.Length()) && 
                   (remaining[separatorIndex] != '\n') &&
                   (remaining[separatorIndex] != '\r'))
                {
                separatorIndex++;
                }
                        
            HBufC* line = HBufC::NewL( separatorIndex );
            TPtrC8 line8 = remaining.Left( separatorIndex );
            line->Des().Copy( line8 );
        
            aContents.Append( line );

            TInt remainingLength = remaining.Length() - separatorIndex;            

            remaining.Set(remaining.Right(remainingLength));            
            }        
        
        contents.Close();
        }
    
    return err;
    }

void CTestAppBase::ReadFileHistory( const TDesC& aHistoryFilename )
    {
    iFileHistory.ResetAndDestroy();
    
    ReadFile( aHistoryFilename, iFileHistory );
    }

void CTestAppBase::AddToFileHistory( const TDesC& aFilename, const TDesC& aHistoryFilename, TInt aMaxHistoryEntries )
    {
    HBufC* filename = HBufC::NewL( aFilename.Length() );
    filename->Des().Copy( aFilename );
    iFileHistory.Append( filename );

    while( iFileHistory.Count() > aMaxHistoryEntries )
        {
        delete iFileHistory[0];
        iFileHistory.Remove( 0 );
        }    
    
    RFile historyFile;
    TInt err = historyFile.Create(iFs, aHistoryFilename, EFileStream | EFileWrite );    
    if( err == KErrAlreadyExists )
        {
        err = historyFile.Open(iFs, aHistoryFilename, EFileStream | EFileWrite );
        historyFile.SetSize(0);
        }
    
    if( err == KErrNone )
        {
        for( TInt index = 0; index < iFileHistory.Count(); index++ )
            {
            TBuf8<KMaxFileName> filename8;
            filename8.Copy( iFileHistory[index]->Des() );
            historyFile.Write( filename8 );
            historyFile.Write( _L8("\n") );
            }
        }
    
    historyFile.Close();    
    }

void CTestAppBase::DoSelectFileL( TPoint aTopRight,
                                  TSize aWindowSize,
                                  const TDesC& aHeaderText, 
                                  const TFileName& aDirectory, 
                                  TInt aDirectoryLevel, 
                                  TDes& aSelectedDirectory, 
                                  TDes& aSelectedFilename )     
    {
    RPointerArray<TDesC> fileNames;
    
    ReadDirectoryEntriesL( aDirectory, fileNames );

    TInt initialSelectionIndex = 0;
    
    _LIT( KUp, ".." );        
    if( aDirectoryLevel > 0 )
        {
        TFileName* newEntry = new(ELeave) TFileName;
        newEntry->Copy( KUp );            
        fileNames.Insert( newEntry, 0 );
        initialSelectionIndex++;
        }
    
    bool done = false;    
    
    while( !done && (aSelectedFilename.Length() == 0) )
        {
        TInt index = SelectFromListL( aTopRight, aWindowSize, aHeaderText, fileNames, initialSelectionIndex );

        if( index < 0 || index >= fileNames.Count() )
            {
            done = true;
            }
        else if( fileNames[index]->Compare(KUp) == 0 )
            {
            // Go up one directory.  Return to caller without setting aFilename
            break;
            }
        else if( (*fileNames[index])[fileNames[index]->Length()-1] == '\\' )
            {
            // Directory selected.
            TFileName directory;
            directory.Copy( aDirectory );
            directory.Append( *fileNames[index] );
            DoSelectFileL( aTopRight, aWindowSize, aHeaderText, directory, aDirectoryLevel+1, aSelectedDirectory, aSelectedFilename );
            }
        else
            {
            // File selected.                            
            aSelectedDirectory.Copy( aDirectory );
            aSelectedFilename.Copy( *fileNames[index] );
            done = true;
            }
        
        }

    fileNames.ResetAndDestroy();
    }

void CTestAppBase::ReadDirectoryEntriesL( const TFileName& aDirectoryName, RPointerArray<TDesC>& aFileNames )
    {
    aFileNames.ResetAndDestroy();
    
    RDir dir;
    User::LeaveIfError( dir.Open( iFs, aDirectoryName, KEntryAttNormal|KEntryAttDir) );
    
    TEntryArray entries;
    TInt err = KErrNone;
    TInt directoryCount = 0;
    while( err == KErrNone )
        {
        err = dir.Read( entries );
                
        for( TInt index = 0; index < entries.Count(); index++ )
            {
            // It was observed that not all devices sorted the list by default, so do an
            // insertion sort here.            
            TFileName* newEntry = new(ELeave) TFileName;
            newEntry->Copy( entries[index].iName );
            
            // Put the directories first in the list.
            if( entries[index].IsDir())
                {
                newEntry->Append( _L("\\") );
                
                TInt insertionIndex = 0;
                while( insertionIndex < directoryCount && newEntry->CompareF(*aFileNames[insertionIndex]) > 0 )
                    {
                    insertionIndex++;
                    }
                aFileNames.Insert( newEntry, insertionIndex );
                
                directoryCount++;
                }
            else
                {
                TInt insertionIndex = directoryCount;
                while( insertionIndex < aFileNames.Count() && newEntry->CompareF(*aFileNames[insertionIndex]) > 0 )
                    {
                    insertionIndex++;
                    }
                aFileNames.Insert( newEntry, insertionIndex );
                }
            }        
        }      
    
    dir.Close();
    }

void CTestAppBase::DrawHelpText()
    {
    iGc->Activate(*iHelpWindow);

    iHelpWindow->Invalidate();
    iHelpWindow->BeginRedraw();

    iGc->Reset();
    
    iGc->UseFont(iFont);                
    iGc->SetBrushColor(KRgbTransparent);
    
    iGc->Clear();

    if( iHelpSemitransparentBackgroundActive )
        {
        iGc->SetPenColor(KRgbTransparent);
        iGc->SetBrushStyle(CWindowGc::ESolidBrush);
        iGc->SetBrushColor( TRgb(0x7f7f7faf) );
        iGc->DrawRect( TRect(iHelpWindowSize) );
        }

    // KRgbWhite seems to be having problems (0xffffff) in some emulators,
    // but 0xfefefe is working, so use that instead of white.        
    iGc->SetPenColor(0xfefefe);

    const TInt KColumn1 = KHelpWindowBorderPixels;
    const TInt KColumn2 = KColumn1 + iHelpWindowColumn1Width + KHelpWindowSpaceBetweenColumns;
    const TInt KRowIncrement = iFontSize + KHelpWindowSpaceBetweenRows;
    
    TInt row = iFontSize + KHelpWindowBorderPixels;

    for( TInt index = 0; index < KSupportedKeysCount; index++ )
        {
        iGc->SetUnderlineStyle(EUnderlineOff);        
    
        TPtrC text = KeyMapText( index, iCurrentPage ); 

        iGc->DrawText( KeyName(index), TPoint(KColumn1, row) );

        if( index == iSoftkeyIndices[iCurrentPage] )
            {
            iGc->SetUnderlineStyle(EUnderlineOn);
            }
        else
            {
            iGc->SetUnderlineStyle(EUnderlineOff);        
            }

        iGc->DrawText( text, TPoint(KColumn2, row) );
        
        row += KRowIncrement;
        }

    iHelpWindow->EndRedraw();
   
    iGc->Deactivate();
    }

void CTestAppBase::MrccatoCommand( TRemConCoreApiOperationId aOperationId,
                                   TRemConCoreApiButtonAction /*aButtonAct*/ )
    {
    // Treat volume up like the right soft key, and volume down like the left soft key.
    TKeyEvent keyEvent;
    keyEvent.iCode = 0;
    keyEvent.iScanCode = 0;
    keyEvent.iModifiers = 0;
    keyEvent.iRepeats = 0;
    
    switch (aOperationId)
        {
        case ERemConCoreApiVolumeUp:
            keyEvent.iCode = KRightSoftKeyCode;
            iWs.SimulateKeyEvent(keyEvent);
            iWs.Flush();
            break;
        case ERemConCoreApiVolumeDown:
            keyEvent.iCode = KLeftSoftKeyCode;
            iWs.SimulateKeyEvent(keyEvent);
            iWs.Flush();
            break;
        default:
            break;
        }
    }