C++程序  |  1052行  |  32.5 KB

//
// Copyright 2005 The Android Open Source Project
//
// Displays the phone image and handles user input.
//

// For compilers that support precompilation, include "wx/wx.h".
#include "wx/wxprec.h"

// Otherwise, include all standard headers
#ifndef WX_PRECOMP
# include "wx/wx.h"
#endif
#include "wx/image.h"   // needed for Windows build
#include "wx/dcbuffer.h"

#include "LinuxKeys.h"
#include "PhoneWindow.h"
#include "DeviceWindow.h"
#include "PhoneData.h"
#include "PhoneCollection.h"
#include "MainFrame.h"
#include "MyApp.h"

using namespace android;

BEGIN_EVENT_TABLE(PhoneWindow, wxWindow)    // NOT wxDialog
    EVT_ACTIVATE(PhoneWindow::OnActivate)
    //EVT_ACTIVATE_APP(PhoneWindow::OnActivate)
    EVT_CLOSE(PhoneWindow::OnClose)
    EVT_MOVE(PhoneWindow::OnMove)
    EVT_ERASE_BACKGROUND(PhoneWindow::OnErase)
    EVT_PAINT(PhoneWindow::OnPaint)

    EVT_KEY_DOWN(PhoneWindow::OnKeyDown)
    EVT_KEY_UP(PhoneWindow::OnKeyUp)
    EVT_LEFT_DOWN(PhoneWindow::OnMouseLeftDown)
    EVT_LEFT_DCLICK(PhoneWindow::OnMouseLeftDown)
    EVT_LEFT_UP(PhoneWindow::OnMouseLeftUp)
    EVT_RIGHT_DOWN(PhoneWindow::OnMouseRightDown)
    EVT_RIGHT_DCLICK(PhoneWindow::OnMouseRightDown)
    EVT_RIGHT_UP(PhoneWindow::OnMouseRightUp)
    EVT_MOTION(PhoneWindow::OnMouseMotion)
    EVT_LEAVE_WINDOW(PhoneWindow::OnMouseLeaveWindow)
    EVT_TIMER(kVibrateTimerId, PhoneWindow::OnTimer)
END_EVENT_TABLE()


/*
 * Create a new PhoneWindow.  This should be a child of the main frame.
 */
PhoneWindow::PhoneWindow(wxWindow* parent, const wxPoint& posn)
    : wxDialog(parent, wxID_ANY, wxT("Device"), posn, wxDefaultSize,
        wxDEFAULT_DIALOG_STYLE),
      mpMOHViewIndex(-1),
      mpMOHButton(NULL),
      mMouseKeySent(AKEYCODE_UNKNOWN),
      mpViewInfo(NULL),
      mNumViewInfo(0),
      mpDeviceWindow(NULL),
      mNumDeviceWindows(0),
      mPhoneModel(-1),
      mCurrentMode(wxT("(unknown)")),
      mPlacementChecked(false),
      mpParent((MainFrame*)parent),
      mTimer(this, kVibrateTimerId),
      mTrackingTouch(false)
{
    SetBackgroundColour(*wxLIGHT_GREY);
    SetBackgroundStyle(wxBG_STYLE_CUSTOM);

    //SetCursor(wxCursor(wxCURSOR_HAND));     // a bit distracting (pg.276)
}

/*
 * Destroy everything we own.
 *
 * This might be called well after we've been closed and another
 * PhoneWindow has been created, because wxWidgets likes to defer things.
 */
PhoneWindow::~PhoneWindow(void)
{
    //printf("--- ~PhoneWindow %p\n", this);
    delete[] mpViewInfo;
    if (mpDeviceWindow != NULL) {
        for (int i = 0; i < mNumDeviceWindows; i++) {
            /* make sure they don't try to use our member */
            mpDeviceWindow[i]->DeviceManagerClosing();
            /* make sure the child window gets destroyed -- not necessary? */
            mpDeviceWindow[i]->Destroy();
        }

        /* delete our array of pointers */
        delete[] mpDeviceWindow;
    }
}

/*
 * Check for an updated runtime when window becomes active
 */
void PhoneWindow::OnActivate(wxActivateEvent& event)
{
    /*
     * DO NOT do this.  Under Windows, it causes the parent window to get
     * an activate event, which causes our parent to get the focus.  With
     * this bit of code active it is impossible for the phone window to
     * receive user input.
     */
    //GetParent()->AddPendingEvent(event);

    // If we are being deactivated, go ahead and send key up events so that the
    // runtime doesn't think we are holding down the key. Issue #685750
    if (!event.GetActive()) {
        ListIter iter;
        for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ) {
            int32_t keyCode = (*iter).GetKeyCode();
            GetDeviceManager()->SendKeyEvent(keyCode, false);
            iter = mPressedKeys.erase(iter);
        }
    }
}

/*
 * Close the phone window.
 */
void PhoneWindow::OnClose(wxCloseEvent& event)
{
    //printf("--- PhoneWindow::OnClose %p\n", this);
#if 0
    if (mDeviceManager.IsRunning() && !mDeviceManager.IsKillable()) {
        printf("Sim: refusing to close window on external runtime\n");
        event.Veto();
        return;
    }
#endif

    wxRect rect = GetRect();
    printf("Sim: Closing phone window (posn=(%d,%d))\n", rect.x, rect.y);

    /* notify others */
    mpParent->PhoneWindowClosing(rect.x, rect.y);
    mDeviceManager.WindowsClosing();

    /* end it all */
    Destroy();
}

/*
 * Prep the PhoneWindow to display a specific phone model.  Pass in the
 * model index.
 *
 * This gets called whenever the display changes.  This could be a new
 * device with identical characteristics, or a different mode for the same
 * device.
 *
 * The window can be re-used so long as the display characteristics are
 * the same.  If the display characteristics are different, we have to
 * restart the device.
 */
bool PhoneWindow::Setup(int phoneIdx)
{
    wxString fileName;
    PhoneCollection* pCollection = PhoneCollection::GetInstance();

    if (phoneIdx < 0 || phoneIdx >= pCollection->GetPhoneCount()) {
        fprintf(stderr, "Bogus phone index %d\n", phoneIdx);
        return false;
    }

    /*
     * Clear these out so that failure here is noticeable to caller.  We
     * regenerate the ViewInfo array every time, because the set of Views
     * is different for every Mode.
     */
    delete[] mpViewInfo;
    mpViewInfo = NULL;
    mNumViewInfo = -1;

    PhoneData* pPhoneData;
    PhoneMode* pPhoneMode;
    PhoneView* pPhoneView;

    pPhoneData = pCollection->GetPhoneData(phoneIdx);

    pPhoneMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii());
    if (pPhoneMode == NULL) {
        fprintf(stderr, "current mode (%s) not known\n",
            (const char*) GetCurrentMode().ToAscii());
        return false;
    }

    int numViews = pPhoneMode->GetNumViews();
    if (numViews == 0) {
        fprintf(stderr, "Phone %d mode %s has no views\n",
            phoneIdx, pPhoneMode->GetName());
        return false;
    }

    const int kBorder = 2;
    int i;
    int maxHeight = 0;
    int fullWidth = kBorder;
    ViewInfo* pViewInfo;

    pViewInfo = new ViewInfo[numViews];

    /* figure out individual and overall dimensions */
    for (i = 0; i < numViews; i++) {
        pPhoneView = pPhoneMode->GetPhoneView(i);
        if (pPhoneView == NULL) {
            fprintf(stderr, "view %d not found\n", i);
            return false;
        }

        if (!GetDimensions(pPhoneData, pPhoneView, &pViewInfo[i]))
            return false;

        if (maxHeight < pViewInfo[i].GetHeight())
            maxHeight = pViewInfo[i].GetHeight();
        fullWidth += pViewInfo[i].GetWidth() + kBorder;
    }

    /* create the device windows if we don't already have them */
    if (mpDeviceWindow == NULL) {
        mNumDeviceWindows = pPhoneData->GetNumDisplays();
        mpDeviceWindow = new DeviceWindow*[mNumDeviceWindows];
        if (mpDeviceWindow == NULL)
            return false;

        for (i = 0; i < mNumDeviceWindows; i++) {
            mpDeviceWindow[i] = new DeviceWindow(this, &mDeviceManager);
        }
    } else {
        assert(pPhoneData->GetNumDisplays() == mNumDeviceWindows);
    }

    /*
     * Position device windows within their views, taking into account
     * border areas.
     */
    int shift = kBorder;
    for (i = 0; i < numViews; i++) {
        int displayIdx;
        PhoneDisplay* pPhoneDisplay;

        displayIdx = pViewInfo[i].GetDisplayIndex();
        pPhoneDisplay = pPhoneData->GetPhoneDisplay(displayIdx);
        //printf("View %d: display %d\n", i, displayIdx);

        pViewInfo[i].SetX(shift);
        pViewInfo[i].SetY(kBorder);

        mpDeviceWindow[displayIdx]->SetSize(
            pViewInfo[i].GetX() + pViewInfo[i].GetDisplayX(),
            pViewInfo[i].GetY() + pViewInfo[i].GetDisplayY(),
            pPhoneDisplay->GetWidth(), pPhoneDisplay->GetHeight());

        // incr by width of view
        shift += pViewInfo[i].GetWidth() + kBorder;
    }

    /* configure the device manager if it's not already running */
    if (!mDeviceManager.IsInitialized()) {
        mDeviceManager.Init(pPhoneData->GetNumDisplays(), mpParent);

        for (i = 0; i < pPhoneData->GetNumDisplays(); i++) {
            PhoneDisplay* pPhoneDisplay;
            bool res;

            pPhoneDisplay = pPhoneData->GetPhoneDisplay(i);

            res = mDeviceManager.SetDisplayConfig(i, mpDeviceWindow[i],
                pPhoneDisplay->GetWidth(), pPhoneDisplay->GetHeight(),
                pPhoneDisplay->GetFormat(), pPhoneDisplay->GetRefresh());
            if (!res) {
                fprintf(stderr, "Sim: ERROR: could not configure device mgr\n");
                return false;
            }
        }
        const char *kmap = pPhoneData->GetPhoneKeyboard(0)->getKeyMap();
        mDeviceManager.SetKeyboardConfig(kmap);
    } else {
        assert(pPhoneData->GetNumDisplays() == mDeviceManager.GetNumDisplays());
    }

    /*
     * Success.  Finish up.
     */
    mPhoneModel = phoneIdx;
    mpViewInfo = pViewInfo;
    mNumViewInfo = numViews;

    /* set up our window */
    SetClientSize(fullWidth, maxHeight + kBorder * 2);
    SetBackgroundColour(*wxLIGHT_GREY);
    //SetBackgroundColour(*wxBLUE);
    SetTitle(wxString::FromAscii(pPhoneData->GetTitle()));

    SetFocus();     // set keyboard input focus

    return true;
}

/*
 * The device table has been reloaded.  We need to throw out any pointers
 * we had into it and possibly reload some stuff.
 */
void PhoneWindow::DevicesRescanned(void)
{
    mpMOHButton = NULL;
    mpMOHViewIndex = -1;

    /*
     * Re-evaluate phone definition.  There is an implicit assumption
     * that the re-scanned version is compatible with the previous
     * version (i.e. it still exists and has the same screen size).
     *
     * We're also currently assuming that no phone definitions have been
     * added or removed, which is bad -- we should get the new index for
     * for phone by searching for it by name.
     *
     * TODO: don't make these assumptions.
     */
    Setup(mPhoneModel);
}

/*
 * Check the initial placement of the window.  We get one of these messages
 * when the window is first placed, and every time it's moved thereafter.
 *
 * Right now we're just trying to make sure wxWidgets doesn't shove it off
 * the top of the screen under Linux.  Might want to change this to
 * remember the previous placement and put the window back.
 */
void PhoneWindow::OnMove(wxMoveEvent& event)
{
    if (mPlacementChecked)
        return;

    wxPoint point;
    point = event.GetPosition();
    if (point.y < 0) {
        printf("Sim: window is at (%d,%d), adjusting\n", point.x, point.y);
        point.y = 0;
        Move(point);
    }

    mPlacementChecked = true;
}

/*
 * Figure out the dimensions required to contain the specified view.
 *
 * This is usually the size of the background image, but if we can't
 * load it or it's too small just create a trivial window.
 */
bool PhoneWindow::GetDimensions(PhoneData* pPhoneData, PhoneView* pPhoneView,
    ViewInfo* pInfo)
{
    PhoneDisplay* pPhoneDisplay;
    int xoff=0, yoff=0, width, height;
    int displayIdx;

    displayIdx = pPhoneData->GetPhoneDisplayIndex(pPhoneView->GetDisplayName());
    if (displayIdx < 0)
        return false;

    pPhoneDisplay = pPhoneData->GetPhoneDisplay(displayIdx);
    if (pPhoneDisplay == NULL) {
        fprintf(stderr, "display '%s' not found in device '%s'\n",
            pPhoneView->GetDisplayName(), pPhoneData->GetName());
        return false;
    }

    // load images for this phone
    (void) pPhoneView->LoadResources();

    width = height = 0;

    // by convention, the background bitmap is the first image in the list
    if (pPhoneView->GetBkgImageCount() > 0) {
        wxBitmap* pBitmap = pPhoneView->GetBkgImage(0)->GetBitmap();
        if (pBitmap != NULL) {
            // size window to match bitmap
            xoff = pPhoneView->GetXOffset();
            yoff = pPhoneView->GetYOffset();
            width = pBitmap->GetWidth();
            height = pBitmap->GetHeight();
        }
    }

    // no bitmap, or bitmap is smaller than display
    if (width < pPhoneDisplay->GetWidth() ||
        height < pPhoneDisplay->GetHeight())
    {
        // create window to just hold display
        xoff = yoff = 0;
        width = pPhoneDisplay->GetWidth();
        height = pPhoneDisplay->GetHeight();
    }
    if (width <= 0 || height <= 0) {
        fprintf(stderr, "ERROR: couldn't determine display size\n");
        return false;
    }

    pInfo->SetX(0);
    pInfo->SetY(0);             // another function determines these
    pInfo->SetDisplayX(xoff);
    pInfo->SetDisplayY(yoff);
    pInfo->SetWidth(width);
    pInfo->SetHeight(height);
    pInfo->SetDisplayIndex(displayIdx);

    //printf("xoff=%d yoff=%d width=%d height=%d index=%d\n",
    //    pInfo->GetDisplayX(), pInfo->GetDisplayY(),
    //    pInfo->GetWidth(), pInfo->GetHeight(), pInfo->GetDisplayIndex());

    return true;
}

/*
 * Return PhoneData pointer for the current phone model.
 */
PhoneData* PhoneWindow::GetPhoneData(void) const
{
    PhoneCollection* pCollection = PhoneCollection::GetInstance();
    return pCollection->GetPhoneData(mPhoneModel);
}

/*
 * Convert a wxWidgets key code into a device key code.
 *
 * Someday we may want to make this configurable.
 *
 * NOTE: we need to create a mapping between simulator key and desired
 * function.  The "return" key should always mean "select", whether
 * it's a "select" button or pressing in on the d-pad.  Ditto for
 * the arrow keys, whether we have a joystick, d-pad, or four buttons.
 * Each key here should have a set of things that it could possibly be,
 * and we match it up with the set of buttons actually defined for the
 * phone.  [for convenience, need to ensure that buttons need not have
 * an associated image]
 */
int PhoneWindow::ConvertKeyCode(int wxKeyCode) const
{
    switch (wxKeyCode) {
    case WXK_NUMPAD_INSERT:
    case WXK_NUMPAD0:
    case '0':                   return KEY_0;
    case WXK_NUMPAD_HOME:
    case WXK_NUMPAD1:
    case '1':                   return KEY_1;
    case WXK_NUMPAD_UP:
    case WXK_NUMPAD2:
    case '2':                   return KEY_2;
    case WXK_NUMPAD_PRIOR:
    case WXK_NUMPAD3:
    case '3':                   return KEY_3;
    case WXK_NUMPAD_LEFT:
    case WXK_NUMPAD4:
    case '4':                   return KEY_4;
    case WXK_NUMPAD_BEGIN:
    case WXK_NUMPAD5:
    case '5':                   return KEY_5;
    case WXK_NUMPAD_RIGHT:
    case WXK_NUMPAD6:
    case '6':                   return KEY_6;
    case WXK_NUMPAD_END:
    case WXK_NUMPAD7:
    case '7':                   return KEY_7;
    case WXK_NUMPAD_DOWN:
    case WXK_NUMPAD8:
    case '8':                   return KEY_8;
    case WXK_NUMPAD_NEXT:
    case WXK_NUMPAD9:
    case '9':                   return KEY_9;
    case WXK_NUMPAD_MULTIPLY:   return KEY_SWITCHVIDEOMODE; //AKEYCODE_STAR;
    case WXK_LEFT:              return KEY_LEFT;
    case WXK_RIGHT:             return KEY_RIGHT;
    case WXK_UP:                return KEY_UP;
    case WXK_DOWN:              return KEY_DOWN;
    case WXK_NUMPAD_ENTER:      return KEY_REPLY; //AKEYCODE_DPAD_CENTER;
    case WXK_HOME:              return KEY_HOME;
    case WXK_PRIOR:
    case WXK_PAGEUP:            return KEY_MENU; //AKEYCODE_SOFT_LEFT;
    case WXK_NEXT:
    case WXK_PAGEDOWN:          return KEY_KBDILLUMUP; //AKEYCODE_SOFT_RIGHT;
    case WXK_DELETE:            
    case WXK_BACK:              return KEY_BACKSPACE; //AKEYCODE_DEL;
    case WXK_ESCAPE:
    case WXK_END:               return KEY_BACK; //AKEYCODE_BACK;
    case WXK_NUMPAD_DELETE:
    case WXK_NUMPAD_DECIMAL:    return KEY_KBDILLUMTOGGLE; //AKEYCODE_POUND;
    case WXK_SPACE:             return KEY_SPACE; //AKEYCODE_SPACE;
    case WXK_RETURN:            return KEY_ENTER; //AKEYCODE_ENTER;
    case WXK_F3:                return KEY_F3; //AKEYCODE_CALL;
    case WXK_F4:                return KEY_F4; //AKEYCODE_END_CALL;
    case WXK_NUMPAD_ADD:
    case WXK_F5:                return KEY_VOLUMEUP;
    case WXK_NUMPAD_SUBTRACT:
    case WXK_F6:                return KEY_VOLUMEDOWN;
    case WXK_F7:                return KEY_POWER;
    case WXK_F8:                return KEY_CAMERA;
    case 'A':                   return KEY_A;
    case 'B':                   return KEY_B;
    case 'C':                   return KEY_C;
    case 'D':                   return KEY_D;
    case 'E':                   return KEY_E;
    case 'F':                   return KEY_F;
    case 'G':                   return KEY_G;
    case 'H':                   return KEY_H;
    case 'I':                   return KEY_I;
    case 'J':                   return KEY_J;
    case 'K':                   return KEY_K;
    case 'L':                   return KEY_L;
    case 'M':                   return KEY_M;
    case 'N':                   return KEY_N;
    case 'O':                   return KEY_O;
    case 'P':                   return KEY_P;
    case 'Q':                   return KEY_Q;
    case 'R':                   return KEY_R;
    case 'S':                   return KEY_S;
    case 'T':                   return KEY_T;
    case 'U':                   return KEY_U;
    case 'V':                   return KEY_V;
    case 'W':                   return KEY_W;
    case 'X':                   return KEY_X;
    case 'Y':                   return KEY_Y;
    case 'Z':                   return KEY_Z;
    case ',':                   return KEY_COMMA;
    case '.':                   return KEY_DOT;
    case '<':                   return KEY_COMMA;
    case '>':                   return KEY_DOT;
    case '`':                   return KEY_GREEN; /*KEY_GRAVE;*/
    case '-':                   return KEY_MINUS;
    case '=':                   return KEY_EQUAL;
    case '[':                   return KEY_LEFTBRACE;
    case ']':                   return KEY_RIGHTBRACE;
    case '\\':                  return KEY_BACKSLASH;
    case ';':                   return KEY_SEMICOLON;
    case '\'':                  return KEY_APOSTROPHE;
    case '/':                   return KEY_SLASH;
    case WXK_SHIFT:             return KEY_LEFTSHIFT;
    case WXK_CONTROL:
    case WXK_ALT:               return KEY_LEFTALT;
    case WXK_TAB:               return KEY_TAB;
    // don't show "ignoring key" message for these
    case WXK_MENU:
        break;
    default:
        printf("(ignoring key %d)\n", wxKeyCode);
        break;
    }

    return AKEYCODE_UNKNOWN;
}


/*
 * Keyboard handling.  These get converted into Android-defined key
 * constants here.
 *
 * NOTE: would be nice to handle menu keyboard accelerators here.
 * Simply stuffing the key events into MainFrame with AddPendingEvent
 * didn't seem to do the trick.
 */
void PhoneWindow::OnKeyDown(wxKeyEvent& event)
{
    int32_t keyCode;

    keyCode = ConvertKeyCode(event.GetKeyCode());
    if (keyCode != AKEYCODE_UNKNOWN) {
        if (!IsKeyPressed(keyCode)) {
            //printf("PW: down: key %d\n", keyCode);
            GetDeviceManager()->SendKeyEvent(keyCode, true);
            AddPressedKey(keyCode);
        }
    } else {
        //printf("PW: down: %d\n", event.GetKeyCode());
        event.Skip();       // not handled by us
    }
}

/*
 * Pass key-up events to runtime.
 */
void PhoneWindow::OnKeyUp(wxKeyEvent& event)
{
    int32_t keyCode;

    keyCode = ConvertKeyCode(event.GetKeyCode());
    if (keyCode != AKEYCODE_UNKNOWN) {
        // Send the key event if we already have this key pressed.
        if (IsKeyPressed(keyCode)) {
            //printf("PW:   up: key %d\n", keyCode);
            GetDeviceManager()->SendKeyEvent(keyCode, false);
            RemovePressedKey(keyCode);
        }
    } else {
        //printf("PW:   up: %d\n", event.GetKeyCode());
        event.Skip();       // not handled by us
    }
}

/*
 * Mouse handling.
 *
 * Unlike more conventional button tracking, we highlight on mouse-over
 * and send the key on mouse-down.  This behavior may be confusing for
 * people expecting standard behavior, but it allows us to simulate the
 * effect of holding a key down.
 *
 * We want to catch both "down" and "double click" events; otherwise
 * fast clicking results in a lot of discarded events.
 */
void PhoneWindow::OnMouseLeftDown(wxMouseEvent& event)
{
    if (mpMOHButton != NULL) {
        //printf("PW: left down\n");
        int32_t keyCode = mpMOHButton->GetKeyCode();
        GetDeviceManager()->SendKeyEvent(keyCode, true);
        mMouseKeySent = keyCode;
        AddPressedKey(keyCode);
    } else {
        int screenX, screenY;

        if (GetTouchPosition(event, &screenX, &screenY)) {
            //printf("TOUCH at %d,%d\n", screenX, screenY);
            mTrackingTouch = true;
            mTouchX = screenX;
            mTouchY = screenY;
            GetDeviceManager()->SendTouchEvent(Simulator::kTouchDown,
                mTouchX, mTouchY);
        } else {
            //printf("(ignoring left click)\n");
        }
    }
}

/*
 * Left button has been released.  Do something clever.
 *
 * On some platforms we will lose this if the mouse leaves the window.
 */
void PhoneWindow::OnMouseLeftUp(wxMouseEvent& WXUNUSED(event))
{
    if (mMouseKeySent != AKEYCODE_UNKNOWN) {
        //printf("PW: left up\n");
        GetDeviceManager()->SendKeyEvent(mMouseKeySent, false);
        RemovePressedKey(mMouseKeySent);
    } else {
        if (mTrackingTouch) {
            //printf("TOUCH release (last was %d,%d)\n", mTouchX, mTouchY);
            mTrackingTouch = false;
            GetDeviceManager()->SendTouchEvent(Simulator::kTouchUp,
                mTouchX, mTouchY);
        } else {
            //printf("(ignoring left-up)\n");
        }
    }
    mMouseKeySent = AKEYCODE_UNKNOWN;
}

void PhoneWindow::OnMouseRightDown(wxMouseEvent& event)
{
    //printf("(ignoring right-down)\n");
}
void PhoneWindow::OnMouseRightUp(wxMouseEvent& event)
{
    //printf("(ignoring right-up)\n");
}

/*
 * Track mouse motion so we can do mouse-over button highlighting.
 */
void PhoneWindow::OnMouseMotion(wxMouseEvent& event)
{
    /*
     * If the mouse motion event occurred inside the device window,
     * we treat it differently than mouse movement over the picture of
     * the device.
     */
    if (event.GetEventObject() == mpDeviceWindow[0]) {
        if (mpMOHViewIndex >= 0) {
            /* can happen if the mouse moves fast enough */
            //printf("Mouse now in dev window, clearing button highlight\n");
            mpMOHViewIndex = -1;
            mpMOHButton = NULL;
            Refresh();
        }

        if (!event.LeftIsDown() && event.RightIsDown()) {
            /* right-button movement */
            //printf("(ignoring right-drag)\n");
            return;
        }

        //printf("moveto: %d,%d\n", event.m_x, event.m_y);

        int screenX, screenY;
        if (mTrackingTouch) {
            if (GetTouchPosition(event, &screenX, &screenY)) {
                //printf("TOUCH moved to %d,%d\n", screenX, screenY);
                mTouchX = screenX;
                mTouchY = screenY;
                GetDeviceManager()->SendTouchEvent(Simulator::kTouchDrag,
                    mTouchX, mTouchY);
            } else {
                //printf("TOUCH moved off screen\n");
            }
        }

        return;
    }

    PhoneData* pPhoneData = GetPhoneData();
    if (pPhoneData == NULL)
        return;

    /*
     * Check to see if we're on top of a button.  If our "on top of
     * something" state has changed, force a redraw.
     *
     * We have to run through the list of Views and check all of the
     * buttons in each.
     */
    PhoneMode* pMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii());
    if (pMode == NULL)
        return;

    int viewIndex = -1;
    PhoneButton* pHighlight = NULL;
    int i;

    for (i = pMode->GetNumViews()-1; i >= 0; i--) {
        PhoneView* pView = pMode->GetPhoneView(i);
        assert(pView != NULL);

        /* convert from window-relative to view-relative */
        pHighlight = pView->FindButtonHit(event.m_x - mpViewInfo[i].GetX(),
                                          event.m_y - mpViewInfo[i].GetY());
        if (pHighlight != NULL) {
            viewIndex = i;
            break;
        }
    }

    if (viewIndex == mpMOHViewIndex && pHighlight == mpMOHButton) {
        /* still hovering over same button */
    } else {
        /* mouse has moved, possibly to a new button */

        mpMOHViewIndex = viewIndex;
        mpMOHButton = pHighlight;

        /* force refresh */
        Refresh();
    }
}

/*
 * Mouse has left the building.  All keys and mouse buttons up.
 *
 * We get one of these if the mouse moves over a child window, such as
 * our DeviceWindow, so it is not the case that we no longer receive
 * key input after getting this event.
 */
void PhoneWindow::OnMouseLeaveWindow(wxMouseEvent& WXUNUSED(event))
{
    //printf("--- mouse is GONE\n");
    ClearPressedKeys();
}

/*
 * Determine device touch screen (x,y) based on window position.
 *
 * Returns "true" if the click corresponds to a location on the display.
 *
 * TODO: should return display index as well -- currently this only
 * supports touch on the main display.
 */
bool PhoneWindow::GetTouchPosition(const wxMouseEvent& event, int* pScreenX,
    int* pScreenY)
{
    /*
     * If the click came from our device window, treat it as a touch.
     */
    if (event.GetEventObject() != mpDeviceWindow[0])
        return false;

    *pScreenX = event.m_x;
    *pScreenY = event.m_y;
    return true;
}

/*
 * We don't want to erase the background now, because it causes flicker
 * under Windows.
 */
void PhoneWindow::OnErase(wxEraseEvent& WXUNUSED(event))
{
    //printf("erase\n");
}

/*
 * Paint the phone and any highlighted buttons.
 *
 * The device output is drawn by DeviceWindow.
 */
void PhoneWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    int view;

    /*
     * Under Mac OS X, the parent window is redrawn every time the child
     * window is redrawn.  This causes poor performance in the simulator.
     * If we're being asked to update a region that corresponds exactly
     * to one of the device output windows, skip the redraw.
     */
    assert(mpViewInfo != NULL);
    for (view = 0; view < mNumViewInfo; view++) {
        int displayIndex;

        displayIndex = mpViewInfo[view].GetDisplayIndex();
        assert(displayIndex >= 0);
        DeviceWindow* pDeviceWindow = mpDeviceWindow[displayIndex];
        assert(pDeviceWindow != NULL);

        wxRect displayRect = pDeviceWindow->GetRect();
        wxRect updateRect = GetUpdateClientRect();

        if (displayRect == updateRect) {
            //printf("(skipping redraw)\n");
            return;
        }
    }

    wxBufferedPaintDC dc(this);

    /*
     * Erase the background to the currently-specified background color.
     */
    wxColour backColor = GetBackgroundColour();
    dc.SetBrush(wxBrush(backColor));
    dc.SetPen(wxPen(backColor, 1));
    wxRect windowRect(wxPoint(0, 0), GetClientSize());
    dc.DrawRectangle(windowRect);

    PhoneData* pPhoneData = GetPhoneData();
    if (pPhoneData == NULL) {
        fprintf(stderr, "OnPaint: no phone data\n");
        return;
    }

    PhoneMode* pPhoneMode;
    PhoneView* pPhoneView;
    int numImages;

    pPhoneMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii());
    if (pPhoneMode == NULL) {
        fprintf(stderr, "current mode (%s) not known\n",
            (const char*) GetCurrentMode().ToAscii());
        return;
    }

    for (view = 0; view < pPhoneMode->GetNumViews(); view++) {
        pPhoneView = pPhoneMode->GetPhoneView(view);
        if (pPhoneView == NULL) {
            fprintf(stderr, "view %d not found\n", view);
            return;
        }

        /* draw background image and "button patches" */
        numImages = pPhoneView->GetBkgImageCount();
        for (int i = 0; i < numImages; i++) {
            const LoadableImage* pLimg = pPhoneView->GetBkgImage(i);
            wxBitmap* pBitmap = pLimg->GetBitmap();
            if (pBitmap != NULL)
                dc.DrawBitmap(*pBitmap,
                    mpViewInfo[view].GetX() + pLimg->GetX(),
                    mpViewInfo[view].GetY() + pLimg->GetY(),
                    TRUE);
        }
    }


    /*
     * Draw button mouse-over highlight.
     *
     * Currently we don't do anything different when the button is held down.
     */
    if (mpMOHViewIndex >= 0 && mpMOHButton != NULL) {
        // button must have graphic, or hit-testing wouldn't have worked
        assert(mpMOHButton->GetHighlightedBitmap() != NULL);
        dc.DrawBitmap(*mpMOHButton->GetHighlightedBitmap(),
            mpViewInfo[mpMOHViewIndex].GetX() + mpMOHButton->GetX(),
            mpViewInfo[mpMOHViewIndex].GetY() + mpMOHButton->GetY(),
            TRUE);
    }

    /*
     * Highlight pressed keys.  We want to do this in all views, because
     * some buttons on the side of the phone might be visible in more
     * than one view.
     */
    for (view = 0; view < pPhoneMode->GetNumViews(); view++) {
        pPhoneView = pPhoneMode->GetPhoneView(view);
        assert(pPhoneView != NULL);

        ListIter iter;
        for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) {
            int32_t keyCode;
            PhoneButton* pButton;

            keyCode = (*iter).GetKeyCode();
            pButton = pPhoneView->FindButtonByKey(keyCode);
            if (pButton != NULL) {
                wxBitmap* pBitmap = pButton->GetSelectedBitmap();
                if (pBitmap != NULL) {
                    dc.DrawBitmap(*pBitmap,
                        mpViewInfo[view].GetX() + pButton->GetX(),
                        mpViewInfo[view].GetY() + pButton->GetY(),
                        TRUE);
                }
            }
        }
    }
}


/*
 * Press a key on the device.
 *
 * Schedules a screen refresh if the set of held-down keys changes.
 */
void PhoneWindow::AddPressedKey(int32_t keyCode)
{
    /*
     * See if the key is already down.  This usually means that the key
     * repeat has kicked into gear.  It could also mean that we
     * missed the key-up event, or the user has hit the same device
     * key with both mouse and keyboard.  Either way, we don't add it
     * a second time.  This way, if we did lose a key-up somehow, they
     * can "clear" the stuck key by hitting it again.
     */
    if (keyCode == AKEYCODE_UNKNOWN) {
        //printf("--- not adding AKEYCODE_UNKNOWN!\n");
        return;
    }

    ListIter iter;
    for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) {
        if ((*iter).GetKeyCode() == keyCode)
            break;
    }
    if (iter == mPressedKeys.end()) {
        KeyInfo newInfo;
        newInfo.SetKeyCode(keyCode);
        mPressedKeys.push_back(newInfo);
        //printf("---  added down=%d\n", keyCode);
        Refresh();      // redraw w/ highlight
    } else {
        //printf("---  already have down=%d\n", keyCode);
    }
}

/*
 * Release a key on the device.
 *
 * Schedules a screen refresh if the set of held-down keys changes.
 */
void PhoneWindow::RemovePressedKey(int32_t keyCode)
{
    /*
     * Release the key.  If it's not in the list, we either missed a
     * key-down event, or the user used both mouse and keyboard and we
     * removed the key when the first device went up.
     */
    ListIter iter;
    for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) {
        if ((*iter).GetKeyCode() == keyCode) {
            mPressedKeys.erase(iter);
            //printf("---  removing down=%d\n", keyCode);
            Refresh();      // redraw w/o highlight
            break;
        }
    }
    if (iter == mPressedKeys.end()) {
        //printf("---  didn't find down=%d\n", keyCode);
    }
}

/*
 * Clear the set of keys that we think are being held down.
 */
void PhoneWindow::ClearPressedKeys(void)
{
    //printf("--- All keys up (count=%d)\n", mPressedKeys.size());

    if (!mPressedKeys.empty()) {
        ListIter iter = mPressedKeys.begin();
        while (iter != mPressedKeys.end()) {
            int32_t keyCode = (*iter).GetKeyCode();
            GetDeviceManager()->SendKeyEvent(keyCode, false);
            iter = mPressedKeys.erase(iter);
        }
        Refresh();
    }
}

/*
 * Returns "true" if the specified key is currently pressed.
 */
bool PhoneWindow::IsKeyPressed(int32_t keyCode)
{
    ListIter iter;
    for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) {
        if ((*iter).GetKeyCode() == keyCode)
            return true;
    }
    return false;
}

void PhoneWindow::Vibrate(int vibrateOn)
{
    wxRect rect = GetRect();
    if(vibrateOn)
    {
        mVibrateX = 0;
        mTimer.Start(25);      // arg is delay in ms
        Move(rect.x-2,rect.y);
    }
    else if(mTimer.IsRunning())
    {
        mTimer.Stop();
        if(mVibrateX&1)
            Move(rect.x-2,rect.y);
        else
            Move(rect.x+2,rect.y);
    }
}

void PhoneWindow::OnTimer(wxTimerEvent& event)
{
    wxRect rect = GetRect();
    mVibrateX++;
    if(mVibrateX&1)
        Move(rect.x+4,rect.y);
    else
        Move(rect.x-4,rect.y);
}