C++程序  |  1463行  |  41.9 KB

//
// Copyright 2005 The Android Open Source Project
//
// Main window, menu bar, and associated goodies.
//

// 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/button.h"
#include "wx/help.h"
#include "wx/filedlg.h"
#include "wx/slider.h"
#include "wx/textctrl.h"

#include "MainFrame.h"
#include "MyApp.h"
#include "Resource.h"
#include "PhoneCollection.h"
#include "PhoneData.h"
#include "PhoneWindow.h"
#include "DeviceWindow.h"
#include "UserEventMessage.h"
#include "PrefsDialog.h"

#include "SimRuntime.h"


static wxString kStatusNotRunning = wxT("Idle");
static wxString kStatusRunning = wxT("Run");

static wxString kDeviceMenuString = wxT("&Device");

static const wxString gStdJavaApps[] = {
    wxT(""),
    wxT("com.android.testharness.TestList"),
    wxT("com.android.apps.contacts.ContactsList"),
    wxT("mikeapp")
};


BEGIN_EVENT_TABLE(MainFrame::MainFrame, wxFrame)
    EVT_CLOSE(MainFrame::OnClose)
    EVT_TIMER(kHalfSecondTimerId, MainFrame::OnTimer)
    //EVT_IDLE(MainFrame::OnIdle)
  
    EVT_ACTIVATE(MainFrame::OnActivate)
    EVT_ACTIVATE_APP(MainFrame::OnActivate)
    EVT_COMBOBOX(IDC_MODE_SELECT, MainFrame::OnComboBox)
    EVT_COMBOBOX(IDC_JAVA_VM, MainFrame::OnComboBox)
    EVT_CHECKBOX(IDC_USE_GDB, MainFrame::OnCheckBox)
    EVT_CHECKBOX(IDC_USE_VALGRIND, MainFrame::OnCheckBox)
    EVT_CHECKBOX(IDC_CHECK_JNI, MainFrame::OnCheckBox)
    EVT_CHECKBOX(IDC_OVERLAY_ONION_SKIN, MainFrame::OnCheckBox)
    EVT_TEXT(IDC_JAVA_APP_NAME, MainFrame::OnText)
    EVT_TEXT_ENTER(IDC_ONION_SKIN_FILE_NAME, MainFrame::OnTextEnter)
    EVT_BUTTON(IDC_ONION_SKIN_BUTTON, MainFrame::OnButton)
    EVT_COMMAND_SCROLL(IDC_ONION_SKIN_ALPHA_VAL, MainFrame::OnSliderChange)

    EVT_MENU(IDM_FILE_PREFERENCES, MainFrame::OnFilePreferences)
    EVT_MENU(IDM_FILE_EXIT, MainFrame::OnFileExit)
    EVT_MENU(IDM_RUNTIME_START, MainFrame::OnSimStart)
    EVT_UPDATE_UI(IDM_RUNTIME_START, MainFrame::OnUpdateSimStart)
    EVT_MENU(IDM_RUNTIME_STOP, MainFrame::OnSimStop)
    EVT_UPDATE_UI(IDM_RUNTIME_STOP, MainFrame::OnUpdateSimStop)
    EVT_MENU(IDM_RUNTIME_RESTART, MainFrame::OnSimRestart)
    EVT_UPDATE_UI(IDM_RUNTIME_RESTART, MainFrame::OnUpdateSimRestart)
    EVT_MENU(IDM_RUNTIME_KILL, MainFrame::OnSimKill)
    EVT_UPDATE_UI(IDM_RUNTIME_KILL, MainFrame::OnUpdateSimKill)
    EVT_MENU_RANGE(IDM_DEVICE_SEL0, IDM_DEVICE_SELN,
        MainFrame::OnDeviceSelected)
    EVT_MENU(IDM_DEVICE_RESCAN, MainFrame::OnDeviceRescan)
    EVT_UPDATE_UI(IDM_DEBUG_SHOW_LOG, MainFrame::OnUpdateDebugShowLog)
    EVT_MENU(IDM_DEBUG_SHOW_LOG, MainFrame::OnDebugShowLog)
    EVT_MENU(IDM_HELP_CONTENTS, MainFrame::OnHelpContents)
    EVT_MENU(IDM_HELP_ABOUT, MainFrame::OnHelpAbout)

    EVT_USER_EVENT(MainFrame::OnUserEvent)
END_EVENT_TABLE()


/*
 * Main window constructor.
 *
 * Creates menus and status bar.
 */
MainFrame::MainFrame(const wxString& title, const wxPoint& pos,
    const wxSize& size, long style)
    : wxFrame((wxFrame *)NULL, -1, title, pos, size, style),
      mSimRunning(false),
      mRestartRequested(false),
      mpPhoneWindow(NULL),
      mPhoneWindowPosn(wxDefaultPosition),
      mTimer(this, kHalfSecondTimerId)
{
    mSimAssetPath = ((MyApp*)wxTheApp)->GetSimAssetPath();
    mSimAssetPath += wxT("/simulator/default/default");

    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    int val;

    val = mPhoneWindowPosn.x;
    pPrefs->GetInt("window-device-x", &val);
    mPhoneWindowPosn.x = val;
    val = mPhoneWindowPosn.y;
    pPrefs->GetInt("window-device-y", &val);
    mPhoneWindowPosn.y = val;

    /*
     * Create main menu.
     */
    ConstructMenu();

    /*
     * Create the status bar.
     */
    int widths[2] = { -1, 50 };
    CreateStatusBar(2, wxFULL_REPAINT_ON_RESIZE);   // no wxST_SIZEGRIP
    SetStatusWidths(2, widths);
    SetStatusText(wxT("Ready"));
    SetStatusText(kStatusNotRunning, 1);

    /*
     * Create main window controls.
     */
    ConstructControls();

#if 0
    /*
     * Use the standard window color for the main frame (which usually
     * has a darker color).  This has a dramatic effect under Windows.
     */
    wxColour color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
    SetOwnBackgroundColour(color);
#endif

    /*
     * Create the log window.
     */
    wxRect layout = LogWindow::GetPrefWindowRect();
    mpLogWindow = new LogWindow(this);
    mpLogWindow->Move(layout.GetTopLeft());
    mpLogWindow->SetSize(layout.GetSize());
    bool showLogWindow = true;
    pPrefs->GetBool("window-log-show", &showLogWindow);
    if (showLogWindow)
        mpLogWindow->Show();

    /*
     * Set up a frequent timer.  We use this to keep our "run/idle"
     * display up to date.  (Ideally this will go away.)
     */
    mTimer.Start(400);      // arg is delay in ms

    /*
     * Handle auto-power-on by sending ourselves an event.  That way it
     * gets handled after window initialization finishes.
     */
    bool autoPowerOn = false;
    pPrefs->GetBool("auto-power-on", &autoPowerOn);
    if (autoPowerOn) {
        printf("Sim: Auto power-up\n");
        wxCommandEvent startEvent(wxEVT_COMMAND_MENU_SELECTED, IDM_RUNTIME_START);
        this->AddPendingEvent(startEvent);
    }

    /*
     * wxThread wants these to be on the heap -- it will call delete on the
     * object when the thread exits.
     */
    mExternalRuntimeThread = new ExternalRuntime();
    mExternalRuntimeThread->StartThread();
    mPropertyServerThread = new PropertyServer();
    mPropertyServerThread->StartThread();
}

/*
 * Construct the main menu.  Called from the constructor.
 */
void MainFrame::ConstructMenu(void)
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();

    /*
     * Scan for available phones.
     */
    PhoneCollection* pCollection = PhoneCollection::GetInstance();
    pCollection->ScanForPhones(mSimAssetPath.ToAscii());

    /*
     * Create the "File" menu.
     */
    wxMenu* menuFile = new wxMenu;

    menuFile->Append(IDM_FILE_PREFERENCES, wxT("&Preferences..."),
        wxT("Edit simulator preferences"));
    menuFile->AppendSeparator();
    menuFile->Append(IDM_FILE_EXIT, wxT("E&xit\tCtrl-Q"),
        wxT("Stop simulator and exit"));

    /*
     * Create the "Runtime" menu.
     */
    wxMenu* menuRuntime = new wxMenu;
    menuRuntime->Append(IDM_RUNTIME_START, wxT("&Power On\tCtrl-G"),
        wxT("Start the device"));
//    menuRuntime->Append(IDM_RUNTIME_STOP, wxT("Power &Off"),
//        wxT("Stop the device"));
    menuRuntime->AppendSeparator();
//    menuRuntime->Append(IDM_RUNTIME_RESTART, wxT("&Restart"),
//        wxT("Restart the device"));
    menuRuntime->Append(IDM_RUNTIME_KILL, wxT("&Kill\tCtrl-K"),
        wxT("Kill the runtime processes"));

    /*
     * Create "Device" menu.
     */
    wxString defaultDevice = wxT("Sooner");
    pPrefs->GetString("default-device", /*ref*/ defaultDevice);
    wxMenu* menuDevice = CreateDeviceMenu(defaultDevice.ToAscii());

    /*
     * Create "Debug" menu.
     */
    wxMenu* menuDebug = new wxMenu;
    menuDebug->AppendCheckItem(IDM_DEBUG_SHOW_LOG, wxT("View &Log Output"),
        wxT("View log output window"));

    /*
     * Create the "Help" menu.
     */
    wxMenu* menuHelp = new wxMenu;
    menuHelp->Append(IDM_HELP_CONTENTS, wxT("&Contents...\tF1"),
        wxT("Simulator help"));
    menuHelp->AppendSeparator();
    menuHelp->Append(IDM_HELP_ABOUT, wxT("&About..."),
        wxT("See the fabulous 'about' box"));

    /*
     * Create the menu bar.
     */
    wxMenuBar *menuBar = new wxMenuBar;
    menuBar->Append(menuFile, wxT("&File"));
    menuBar->Append(menuDevice, kDeviceMenuString);
    menuBar->Append(menuRuntime, wxT("&Runtime"));
    menuBar->Append(menuDebug, wxT("&Debug"));
    menuBar->Append(menuHelp, wxT("&Help"));

    SetMenuBar(menuBar);

}

/*
 * Construct the "device" menu from our phone collection.
 */
wxMenu* MainFrame::CreateDeviceMenu(const char* defaultItemName)
{
    wxMenu* menuDevice = new wxMenu;
    PhoneCollection* pCollection = PhoneCollection::GetInstance();
    int defaultModel = 0;

    for (int i = 0; i < pCollection->GetPhoneCount(); i++) {
        PhoneData* pPhoneData = pCollection->GetPhoneData(i);
        assert(pPhoneData != NULL);

        menuDevice->AppendRadioItem(IDM_DEVICE_SEL0 + i,
            wxString::FromAscii(pPhoneData->GetTitle()));

        // use this one as default if the string matches
        if (strcasecmp(pPhoneData->GetName(), defaultItemName) == 0)
            defaultModel = i;
    }

    menuDevice->Check(IDM_DEVICE_SEL0 + defaultModel, true);

    menuDevice->AppendSeparator();
    menuDevice->Append(IDM_DEVICE_RESCAN, wxT("Re-scan"));

    return menuDevice;
}

/*
 * Create some controls in the main window.
 *
 * The main frame doesn't use the normal background color that you find
 * in dialog windows, so we create a "panel" and put all the controls
 * on that.
 */
void MainFrame::ConstructControls(void)
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    wxPanel* base = new wxPanel(this, wxID_ANY);
    wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL);
    wxBoxSizer* tmpSizer;
    wxStaticBoxSizer* displayOptSizer;
    wxStaticBoxSizer* runtimeOptSizer;
    wxStaticBoxSizer* onionSkinOptSizer;
    wxComboBox* pModeSelection;
    wxCheckBox* pUseGDB;
    wxCheckBox* pUseValgrind;
    wxCheckBox* pCheckJni;
    wxCheckBox* pOverlayOnionSkin;
    
    displayOptSizer = new wxStaticBoxSizer(wxHORIZONTAL, base,
        wxT("Configuration"));
    runtimeOptSizer = new wxStaticBoxSizer(wxVERTICAL, base,
        wxT("Runtime Options"));
    onionSkinOptSizer = new wxStaticBoxSizer(wxVERTICAL, base,
        wxT("Onion Skin Options"));

    /*
     * Set up the configuration sizer (nee "display options").
     */
    tmpSizer = new wxBoxSizer(wxHORIZONTAL);
    displayOptSizer->Add(tmpSizer);
    tmpSizer->Add(
            new wxStaticText(base, wxID_ANY, wxT("Device mode:"),
            wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), 0, wxALIGN_CENTER_VERTICAL);
    pModeSelection = new wxComboBox(base, IDC_MODE_SELECT, wxT(""),
            wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY);
    tmpSizer->AddSpacer(kInterSpacing);
    tmpSizer->Add(pModeSelection);

    displayOptSizer->AddSpacer(kInterSpacing);

    /*
     * Configure the runtime options sizer.
     */
    wxComboBox* pJavaAppName;
    tmpSizer = new wxBoxSizer(wxHORIZONTAL);
    pUseGDB = new wxCheckBox(base, IDC_USE_GDB, wxT("Use &debugger"));
    tmpSizer->Add(pUseGDB);
    tmpSizer->AddSpacer(kInterSpacing);
    pUseValgrind = new wxCheckBox(base, IDC_USE_VALGRIND, wxT("Use &valgrind"));
    tmpSizer->Add(pUseValgrind);
    tmpSizer->AddSpacer(kInterSpacing);
    pCheckJni = new wxCheckBox(base, IDC_CHECK_JNI, wxT("Check &JNI"));
    tmpSizer->Add(pCheckJni);

    pJavaAppName = new wxComboBox(base, IDC_JAVA_APP_NAME, wxT(""),
        wxDefaultPosition, wxSize(320, -1), NELEM(gStdJavaApps), gStdJavaApps,
        wxCB_DROPDOWN);
    wxBoxSizer* javaAppSizer = new wxBoxSizer(wxHORIZONTAL);
    javaAppSizer->Add(
            new wxStaticText(base, wxID_ANY,
                wxT("Java app:"),
                wxDefaultPosition, wxDefaultSize,
                wxALIGN_LEFT),
            0, wxALIGN_CENTER_VERTICAL);
    javaAppSizer->AddSpacer(kInterSpacing);
    javaAppSizer->Add(pJavaAppName);

    runtimeOptSizer->Add(tmpSizer);

    runtimeOptSizer->AddSpacer(kInterSpacing);
    runtimeOptSizer->Add(javaAppSizer);
    runtimeOptSizer->AddSpacer(kInterSpacing);

    wxString tmpStr;
    SetCheckFromPref(pUseGDB, "debug", false);
    SetCheckFromPref(pUseValgrind, "valgrind", false);
    SetCheckFromPref(pCheckJni, "check-jni", false);
    if (pPrefs->GetString("java-app-name", /*ref*/ tmpStr))
        pJavaAppName->SetValue(tmpStr);

    /*
     * Configure the onion skin options sizer.
     */
    wxTextCtrl* pOnionSkinFileNameText;
    wxButton* pOnionSkinFileButton;
    wxSlider* pOnionSkinAlphaSlider;
    tmpSizer = new wxBoxSizer(wxHORIZONTAL);
    pOverlayOnionSkin = new wxCheckBox(base, 
        IDC_OVERLAY_ONION_SKIN, wxT("Overlay &onion skin"));
    tmpSizer->Add(pOverlayOnionSkin);

    pOnionSkinFileNameText = new wxTextCtrl(base, 
        IDC_ONION_SKIN_FILE_NAME, wxT(""),
        wxDefaultPosition, wxSize(250, -1),
        wxTE_PROCESS_ENTER);
    pOnionSkinFileButton = new wxButton(base, IDC_ONION_SKIN_BUTTON,
        wxT("Choose"));

    wxBoxSizer* onionSkinFileNameSizer = new wxBoxSizer(wxHORIZONTAL);
    onionSkinFileNameSizer->Add(
        new wxStaticText(base, wxID_ANY,
            wxT("Filename:"),
            wxDefaultPosition, wxDefaultSize,
            wxALIGN_LEFT),
        0, wxALIGN_CENTER_VERTICAL);
    onionSkinFileNameSizer->AddSpacer(kInterSpacing);
    onionSkinFileNameSizer->Add(pOnionSkinFileNameText);
    onionSkinFileNameSizer->Add(pOnionSkinFileButton);

    wxBoxSizer * onionSkinAlphaSizer = new wxBoxSizer(wxHORIZONTAL);
    int initialAlphaVal = 127;
    pPrefs->GetInt("onion-skin-alpha-value", &initialAlphaVal);
    pOnionSkinAlphaSlider = new wxSlider(base, IDC_ONION_SKIN_ALPHA_VAL,
        initialAlphaVal, 0, 255, wxDefaultPosition, wxSize(150, 20));
    onionSkinAlphaSizer->Add(
        new wxStaticText(base, wxID_ANY,
            wxT("Transparency:"),
            wxDefaultPosition, wxDefaultSize,
            wxALIGN_LEFT),
        0, wxALIGN_CENTER_VERTICAL);
    onionSkinAlphaSizer->AddSpacer(kInterSpacing);
    onionSkinAlphaSizer->Add(pOnionSkinAlphaSlider, 1, wxCENTRE | wxALL, 5);

    onionSkinOptSizer->Add(tmpSizer);
    onionSkinOptSizer->AddSpacer(kInterSpacing);
    onionSkinOptSizer->Add(onionSkinFileNameSizer);
    onionSkinOptSizer->Add(onionSkinAlphaSizer);

    wxString tmpStr2;
    SetCheckFromPref(pOverlayOnionSkin, "overlay-onion-skin", false);
    if (pPrefs->GetString("onion-skin-file-name", /*ref*/ tmpStr2))
        pOnionSkinFileNameText->SetValue(tmpStr2);

    /*
     * Add the various components to the master sizer.
     */
    masterSizer->Add(displayOptSizer);
    masterSizer->AddSpacer(kInterSpacing * 2);
    masterSizer->Add(runtimeOptSizer);
    masterSizer->AddSpacer(kInterSpacing * 2);
    masterSizer->Add(onionSkinOptSizer);
    //masterSizer->AddSpacer(kInterSpacing);

    /*
     * I don't see a way to guarantee that the window is wide enough to
     * show the entire menu bar, so just throw some pixels at it.
     */
    wxBoxSizer* minWidthSizer = new wxBoxSizer(wxVERTICAL);
    minWidthSizer->Add(300, kEdgeSpacing);       // forces minimum width
    minWidthSizer->Add(masterSizer);
    minWidthSizer->AddSpacer(kInterSpacing * 2);

    /* move us a few pixels in from the left */
    wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL);
    indentSizer->AddSpacer(kEdgeSpacing);
    indentSizer->Add(minWidthSizer);
    indentSizer->AddSpacer(kEdgeSpacing);

    base->SetSizer(indentSizer);

    indentSizer->Fit(this);
    indentSizer->SetSizeHints(this);
}

/*
 * Set the value of a checkbox based on a value from the config file.
 */
void MainFrame::SetCheckFromPref(wxCheckBox* pControl, const char* prefStr,
    bool defaultVal)
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    assert(pPrefs != NULL);

    bool val = defaultVal;
    pPrefs->GetBool(prefStr, &val);

    pControl->SetValue(val);
}

/*
 * Destructor.
 */
MainFrame::~MainFrame(void)
{
    PhoneCollection::DestroyInstance();

    delete mExternalRuntimeThread;
    delete mPropertyServerThread;

    // don't touch mpModeSelection -- child of window
}

/*
 * File->Quit or click on close box.
 *
 * If we want an "are you sure you want to quit" box, add it here.
 */
void MainFrame::OnClose(wxCloseEvent& event)
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();

/*
    if (event.CanVeto())
        printf("Closing (can veto)\n");
    else
        printf("Closing (mandatory)\n");
*/

    /*
     * Generally speaking, Close() is not guaranteed to close the window.
     * However, we want to use it here because (a) our windows are
     * guaranteed to close, and (b) it provides our windows an opportunity
     * to tell others that they are about to vanish.
     */
    if (mpPhoneWindow != NULL)
        mpPhoneWindow->Close(true);

    /* save position of main window */
    wxPoint pos = GetPosition();
    pPrefs->SetInt("window-main-x", pos.x);
    pPrefs->SetInt("window-main-y", pos.y);

    /* save default device selection */
    int idx = GetSelectedDeviceIndex();
    if (idx >= 0) {
        PhoneCollection* pCollection = PhoneCollection::GetInstance();
        PhoneData* pPhoneData = pCollection->GetPhoneData(idx);
        pPrefs->SetString("default-device", pPhoneData->GetName());
    }

    if (mpLogWindow != NULL)
        mpLogWindow->Close(true);
    Destroy();
}

/*
 * File->Preferences
 */
void MainFrame::OnFilePreferences(wxCommandEvent& WXUNUSED(event))
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    PrefsDialog dialog(this);
    int result;

    result = dialog.ShowModal();
    if (result == wxID_OK) {
        /*
         * The dialog handles writing changes to Preferences, so all we
         * need to deal with here are changes that have an immediate
         * impact on us. (which is currently nothing)
         */
    }
}

/*
 * File->Exit
 */
void MainFrame::OnFileExit(wxCommandEvent& WXUNUSED(event))
{
    Close(FALSE);       // false means "allow veto"
}

/*
 * Decide whether Simulator->Start should be enabled.
 */
void MainFrame::OnUpdateSimStart(wxUpdateUIEvent& event)
{
    if (IsRuntimeRunning())
        event.Enable(FALSE);
    else
        event.Enable(TRUE);
}

/*
 * Simulator->Start
 */
void MainFrame::OnSimStart(wxCommandEvent& WXUNUSED(event))
{
    // keyboard equivalents can still get here even if menu item disabled
    if (IsRuntimeRunning())
        return;

    int id = GetSelectedDeviceIndex();
    if (id < 0) {
        fprintf(stderr, "Sim: could not identify currently selected device\n");
        return;
    }

#if 0
    static int foo = 0;
    foo++;
    if (foo == 2) {
        Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();

        pPrefs->SetBool("debug", true);
    }
#endif

    SetupPhoneUI(id, NULL);
    if (mpPhoneWindow != NULL)
        mpPhoneWindow->GetDeviceManager()->StartRuntime();
}

/*
 * Decide whether Simulator->Stop should be enabled.
 */
void MainFrame::OnUpdateSimStop(wxUpdateUIEvent& event)
{
    if (IsRuntimeRunning())
        event.Enable(TRUE);
    else
        event.Enable(FALSE);
}

/*
 * Simulator->Stop
 */
void MainFrame::OnSimStop(wxCommandEvent& WXUNUSED(event))
{
    if (!IsRuntimeRunning())
        return;
    assert(mpPhoneWindow != NULL);
    mpPhoneWindow->GetDeviceManager()->StopRuntime();
}

/*
 * Decide whether Simulator->Restart should be enabled.
 */
void MainFrame::OnUpdateSimRestart(wxUpdateUIEvent& event)
{
    if (IsRuntimeRunning())
        event.Enable(TRUE);
    else
        event.Enable(FALSE);
}

/*
 * Simulator->Restart - stop then start the device runtime.
 */
void MainFrame::OnSimRestart(wxCommandEvent& WXUNUSED(event))
{
    if (!IsRuntimeRunning())
        return;

    printf("Restart requested\n");
    mpPhoneWindow->GetDeviceManager()->StopRuntime();

    mRestartRequested = true;
}

/*
 * Decide whether Simulator->Kill should be enabled.
 */
void MainFrame::OnUpdateSimKill(wxUpdateUIEvent& event)
{
    if (IsRuntimeKillable())
        event.Enable(TRUE);
    else
        event.Enable(FALSE);
}

/*
 * Simulator->Kill
 */
void MainFrame::OnSimKill(wxCommandEvent& WXUNUSED(event))
{
    if (!IsRuntimeKillable())
        return;
    assert(mpPhoneWindow != NULL);
    mpPhoneWindow->GetDeviceManager()->KillRuntime();
}


/*
 * Device->[select]
 */
void MainFrame::OnDeviceSelected(wxCommandEvent& event)
{
    wxBusyCursor busyc;
    int id = event.GetId() - IDM_DEVICE_SEL0;

    SetupPhoneUI(id, NULL);
}

/*
 * Device->Rescan
 */
void MainFrame::OnDeviceRescan(wxCommandEvent& event)
{
    wxBusyCursor busyc;
    wxMenuBar* pMenuBar;
    PhoneCollection* pCollection;
    wxMenu* pOldMenu;
    wxMenu* pNewMenu;
    const char* curDevName = NULL;
    int idx;
    
    /* figure out the current device name */
    pCollection = PhoneCollection::GetInstance();
    idx = GetSelectedDeviceIndex();
    if (idx >= 0) {
        PhoneData* pPhoneData;

        pPhoneData = pCollection->GetPhoneData(idx);
        curDevName = pPhoneData->GetName();
        printf("--- device name is '%s'\n", (const char*) curDevName);
    }

    /* reconstruct device menu with new data */
#ifdef BEFORE_ASSET
    pCollection->ScanForPhones(mSimAssetPath);
#else
    pCollection->ScanForPhones(NULL);
#endif

    pMenuBar = GetMenuBar();
    idx = pMenuBar->FindMenu(kDeviceMenuString);
    if (idx == wxNOT_FOUND) {
        fprintf(stderr, "Sim: couldn't find %s menu\n", (const char*) kDeviceMenuString.ToAscii());
        return;
    }

    pNewMenu = CreateDeviceMenu(curDevName);

    pOldMenu = pMenuBar->Replace(idx, pNewMenu, kDeviceMenuString);
    delete pOldMenu;

    /* tell the PhoneWindow about it; may cause runtime to exit */
    if (mpPhoneWindow != NULL)
        mpPhoneWindow->DevicesRescanned();
}

/*
 * Set checkbox on menu item.
 */
void MainFrame::OnUpdateDebugShowLog(wxUpdateUIEvent& event)
{
    if (mpLogWindow == NULL) {
        event.Enable(false);
    } else {
        event.Enable(true);
        event.Check(mpLogWindow->IsShown());
    }
}

/*
 * Debug->ShowLog toggle.
 */
void MainFrame::OnDebugShowLog(wxCommandEvent& WXUNUSED(event))
{
    mpLogWindow->Show(!mpLogWindow->IsShown());
}

/*
 * Help->Contents
 */
void MainFrame::OnHelpContents(wxCommandEvent& WXUNUSED(event))
{
    ((MyApp*)wxTheApp)->GetHelpController()->DisplayContents();
}

/*
 * Help->About
 */
void MainFrame::OnHelpAbout(wxCommandEvent& WXUNUSED(event))
{
    wxMessageBox(wxT("Android Simulator v0.1\n"
                     "Copyright 2006 The Android Open Source Project"),
        wxT("About..."), wxOK | wxICON_INFORMATION, this);
}

/*
 * Sent from phonewindow or when activated
 */
void MainFrame::OnActivate(wxActivateEvent& event)
{
#if 0
    if (event.GetActive())
    {
        if (mpPhoneWindow != NULL &&
            mpPhoneWindow->GetDeviceManager()->RefreshRuntime())
        {
            wxString msg;
            int sel;

            msg = wxT("Newer runtime executable found. Would you like to reload the device?");

            sel = wxMessageBox(msg, wxT("Android Safety Patrol"),
                wxYES | wxNO | wxICON_QUESTION, mpPhoneWindow);
            //printf("BUTTON was %d (yes=%d)\n", sel, wxYES);
            if (sel == wxYES)
            {
                mpPhoneWindow->GetDeviceManager()->StopRuntime();
                mpPhoneWindow->Close();
                mpPhoneWindow = NULL;
                mRestartRequested = true;
            }
            else
            {
                mpPhoneWindow->GetDeviceManager()->UserCancelledRefresh();
            }
        }
    }
#endif

    // let wxWidgets do whatever it needs to do
    event.Skip();
}
            

/*
 * Device mode selection box.
 */
void MainFrame::OnComboBox(wxCommandEvent& event)
{
    const char* pref;
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    assert(pPrefs != NULL);

    if (IDC_MODE_SELECT == event.GetId())
    {
        int id = GetSelectedDeviceIndex();
        if (id < 0)
            return;
        //printf("--- mode selected: '%s'\n", (const char*) event.GetString().ToAscii());

        /*
         * Call the phone window's setup function.  Don't call our SetupPhoneUI
         * function from here -- updating the combo box from a combo box callback
         * could cause problems.
         */
        if (mpPhoneWindow != NULL) {
            mpPhoneWindow->SetCurrentMode(event.GetString());
            mpPhoneWindow->Setup(id);
        }
    } else if (event.GetId() == IDC_JAVA_VM) {
        wxComboBox* pBox = (wxComboBox*) FindWindow(IDC_JAVA_VM);
        pPrefs->SetString("java-vm", pBox->GetValue().ToAscii());
    }
}

/*
 * One of our option checkboxes has been changed.
 *
 * We update the prefs database so that the settings are retained when
 * the simulator is next used.
 */
void MainFrame::OnCheckBox(wxCommandEvent& event)
{
    const char* pref;

    switch (event.GetId()) {
    case IDC_USE_GDB:               pref = "debug";                 break;
    case IDC_USE_VALGRIND:          pref = "valgrind";              break;
    case IDC_CHECK_JNI:             pref = "check-jni";             break;
    case IDC_OVERLAY_ONION_SKIN:    pref = "overlay-onion-skin";    break; 
    default:
        printf("Sim: unrecognized checkbox %d in OnCheckBox\n", event.GetId());
        return;
    }

    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    assert(pPrefs != NULL);

    pPrefs->SetBool(pref, (bool) event.GetInt());
    //printf("--- set pref '%s' to %d\n", pref, (bool) event.GetInt());
    if (event.GetId() == IDC_OVERLAY_ONION_SKIN) {
        BroadcastOnionSkinUpdate();
    }
    if (event.GetId() == IDC_CHECK_JNI) {
        const char* val = "0";
        if ((bool) event.GetInt())
            val = "1";
        mPropertyServerThread->SetProperty(PropertyServer::kPropCheckJni, val);

    }
}

void MainFrame::BroadcastOnionSkinUpdate() {
    if (mpPhoneWindow != NULL) {
        // broadcast a user event indicating an onion skin update
        UserEvent uev(0, (void*) -1);
        mpPhoneWindow->GetDeviceManager()->BroadcastEvent(uev);
    }
}

/*
 * A text control on the main page is being updated.
 *
 * The current implementation updates the preferences database on every
 * change, which is a bit silly but is easy to do.
 */
void MainFrame::OnText(wxCommandEvent& event)
{
    const char* pref;

    switch (event.GetId()) {
    case IDC_JAVA_APP_NAME:     pref = "java-app-name"; break;
    default:
        printf("Sim: unrecognized textctrl %d in OnText\n", event.GetId());
        return;
    }

    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    assert(pPrefs != NULL);

    // event.GetString() does not work on Mac -- always blank
    //pPrefs->SetString(pref, event.GetString());
    assert(event.GetId() == IDC_JAVA_APP_NAME); // fix if we add more
    wxComboBox* pBox;
    pBox = (wxComboBox*) FindWindow(IDC_JAVA_APP_NAME);
    pPrefs->SetString(pref, pBox->GetValue().ToAscii());
    //printf("--- set pref '%s' to '%s'\n", pref,(const char*)pBox->GetValue());
}

/*
 * A user pressed enter in a text control on the main page.
 *
 * The current implementation updates the preferences database on every
 * change, which is a bit silly but is easy to do.
 */
void MainFrame::OnTextEnter(wxCommandEvent& event)
{
    const char* pref;

    switch (event.GetId()) {
    case IDC_ONION_SKIN_FILE_NAME:
        pref = "onion-skin-file-name";
        break;
    default:
        printf("Sim: unrecognized textctrl %d in OnTextEnter\n", event.GetId());
        return;
    }

    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    assert(pPrefs != NULL);

    assert(event.GetId() == IDC_ONION_SKIN_FILE_NAME); // fix if we add more
    wxTextCtrl* pTextCtrl;
    pTextCtrl = (wxTextCtrl*) FindWindow(IDC_ONION_SKIN_FILE_NAME);
    wxString onionSkinFileNameWxString = pTextCtrl->GetValue();
    char* onionSkinFileName = "";
    if (onionSkinFileNameWxString.Len() > 0) {
        onionSkinFileName = android::strdupNew(onionSkinFileNameWxString.ToAscii());
    }
    pPrefs->SetString(pref, onionSkinFileName);
    BroadcastOnionSkinUpdate();
}

/*
 * A user pressed a button on the main page
 * 
 */
 void MainFrame::OnButton(wxCommandEvent& event)
 {
    wxWindow* base;
    wxFileDialog* pOnionSkinFileChooser;
    int retVal;
    switch (event.GetId()) {
    case IDC_ONION_SKIN_BUTTON:
        base = FindWindow(IDC_ONION_SKIN_BUTTON)->GetParent();
        pOnionSkinFileChooser = new wxFileDialog(base, 
            wxT("Choose the onion skin image file."), 
            wxT(""), wxT(""), wxT("*.*"),
            wxOPEN | wxFILE_MUST_EXIST);
        retVal = pOnionSkinFileChooser->ShowModal();
        if (retVal == pOnionSkinFileChooser->GetAffirmativeId()) {
            Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
            assert(pPrefs != NULL);
            wxString fileNameWxString = pOnionSkinFileChooser->GetPath();
            const char* fileName = android::strdupNew(fileNameWxString.ToAscii());
            wxTextCtrl* fileTextCtrl = (wxTextCtrl*) FindWindow(IDC_ONION_SKIN_FILE_NAME);
            fileTextCtrl->SetValue(fileNameWxString);
            pPrefs->SetString("onion-skin-file-name", fileName);
            BroadcastOnionSkinUpdate();
        }
        break;
    default:
        printf("Sim: unrecognized button %d in OnButton\n", event.GetId());
        return;
    }     
 }
 
 /*
  * The user moved a slider on the main page
  */
 void MainFrame::OnSliderChange(wxScrollEvent& event)
 {
    wxSlider* pSlider;
    Preferences* pPrefs;
    switch (event.GetId()) {
    case IDC_ONION_SKIN_ALPHA_VAL:
        pSlider = (wxSlider*) FindWindow(IDC_ONION_SKIN_ALPHA_VAL);
        pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
        assert(pPrefs != NULL);
        pPrefs->SetInt("onion-skin-alpha-value", pSlider->GetValue());
        BroadcastOnionSkinUpdate();
        break;
    default:
        printf("Sim: unrecognized scroller or slider %d in OnSliderChange\n", event.GetId());
        return;
    }     
 }

#if 0
/*
 * Idle processing.  Under wxWidgets this only called once after UI
 * activity unless you call event.RequestMore().
 */
void MainFrame::OnIdle(wxIdleEvent& event)
{
    event.Skip();       // let base class handler do stuff
}
#endif

/*
 * Handle the timer.
 *
 * This is being called in the main thread, so multithreading with the
 * rest of MainFrame isn't a concern here.
 */
void MainFrame::OnTimer(wxTimerEvent& event)
{
    bool status;

    /*
     * Check to see if the runtime died without telling us.  This can only
     * happen if we forcibly kill our thread.  We shouldn't really be
     * doing that anymore, but keep this in for now just in case.
     */
    status = IsRuntimeRunning();

    if (mSimRunning != status) {
        if (!status) {
            printf("Sim: fixed mSimRunning=%d actual=%d\n",
                mSimRunning, status);
            mSimRunning = status;

            if (!status)
                HandleRuntimeStop();
        } else {
            /*
             * This was happening when we were shutting down but the
             * device management thread hadn't completely gone away.  The
             * simple IsRunning test passes, so we get a false positive.
             * Ignore it.
             */
        }
    }

    if (gWantToKill) {
        if (IsRuntimeRunning()) {
            printf("Sim: handling kill request\n");
            mpPhoneWindow->GetDeviceManager()->KillRuntime();
        }
        gWantToKill = false;

        /* see if Ctrl-C should kill us too */
        Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
        bool die = false;

        pPrefs->GetBool("trap-sigint-suicide", &die);
        if (die) {
            printf("Sim: goodbye cruel world!\n");
            exit(0);
        }
    }
}

/*
 * Determine whether or not the simulator is running.
 */
bool MainFrame::IsRuntimeRunning(void)
{
    bool result;

    if (mpPhoneWindow == NULL)
        result = false;
    else if (!mpPhoneWindow->IsReady())
        result = false;
    else
        result = mpPhoneWindow->GetDeviceManager()->IsRunning();

    return result;
}

/*
 * Determine whether or not the runtime can be killed.
 */
bool MainFrame::IsRuntimeKillable(void)
{
    bool result;

    result = IsRuntimeRunning();
    if (result)
        result = mpPhoneWindow->GetDeviceManager()->IsKillable();

    return result;
}

/*
 * Determine whether two devices are sufficiently compatible.
 */
bool MainFrame::CompatibleDevices(PhoneData* pData1, PhoneData* pData2)
{
    int displayCount;

    displayCount = pData1->GetNumDisplays();
    if (pData2->GetNumDisplays() != displayCount)
        return false;

    for (int i = 0; i < displayCount; i++) {
        PhoneDisplay* pDisplay1 = pData1->GetPhoneDisplay(i);
        PhoneDisplay* pDisplay2 = pData2->GetPhoneDisplay(i);

        if (!PhoneDisplay::IsCompatible(pDisplay1, pDisplay2))
            return false;
    }

    return true;
}

/*
 * (Re-)arrange the UI for the currently selected phone model.
 *
 * If the simulator is running, and the set of displays for the current
 * device are incompatible with the new device, we need to restart the
 * runtime.  We need to ask for permission first though.
 */
void MainFrame::SetupPhoneUI(int idx, const char* defaultMode)
{
    PhoneCollection* pCollection;
    PhoneData* pPhoneData;
    wxString* choices = NULL;
    int numChoices = 0;
    int numKeyboards = 0;
    bool haveDefaultMode = false;
    wxCharBuffer currentMode;
    int i;

    pCollection = PhoneCollection::GetInstance();
    pPhoneData = pCollection->GetPhoneData(idx);
    if (pPhoneData == NULL) {
        fprintf(stderr, "ERROR: device index %d not valid\n", idx);
        goto bail;
    }

    /*
     * We have a window up.  If the displays aren't compatible, we'll
     * need to recreate it.
     */
    if (mpPhoneWindow != NULL) {
        PhoneData* pCurData = mpPhoneWindow->GetPhoneData();

        if (!CompatibleDevices(pCurData, pPhoneData)) {
            /*
             * We need to trash the window.  This will also kill the
             * runtime.  If it's running, ask permission.
             */
            if (IsRuntimeRunning()) {
                wxString msg;
                int sel;

                msg =  wxT("Switching to the new device requires restarting the");
                msg += wxT(" runtime.  Continue?");

                sel = wxMessageBox(msg, wxT("Android Safety Patrol"),
                    wxOK | wxCANCEL | wxICON_QUESTION, this);
                printf("BUTTON was %d (ok=%d)\n", sel, wxOK);
                if (sel == wxCANCEL)
                    goto bail;

                /* shut it down (politely), ask for an eventual restart */
                mpPhoneWindow->GetDeviceManager()->StopRuntime();
                mpPhoneWindow->Close();
                mpPhoneWindow = NULL;
                mRestartRequested = true;
                goto bail;
            } else {
                /* not running, just trash the window and continue */
                mpPhoneWindow->Close();
                mpPhoneWindow = NULL;
            }
        }
    }

    /*
     * Figure out the set of available modes.
     */

    numChoices = pPhoneData->GetNumModes();
    if (numChoices > 0) {
        choices = new wxString[numChoices];
        for (i = 0; i < numChoices; i++) {
            PhoneMode* pPhoneMode;
            pPhoneMode = pPhoneData->GetPhoneMode(i);
            choices[i] = wxString::FromAscii(pPhoneMode->GetName());
            if (defaultMode != NULL &&
                strcmp(defaultMode, pPhoneMode->GetName()) == 0)
            {
                haveDefaultMode = true;
            }
        }
    }

    if (choices == NULL) {
        /* had a failure earlier; configure UI with default stuff */
        choices = new wxString[1];
        choices[0] = wxT("(none)");
    }

    if (!haveDefaultMode) {
        /*
         * Default mode wasn't found.  If we specify it as the default
         * in the wxComboBox create call it shows up in the combo box
         * under Linux, even if it doesn't exist in the list.  So, we
         * make sure that it doesn't get used if we can't find it.
         */
        if (defaultMode != NULL) {
            printf("Sim: HEY: default mode '%s' not found in list\n",
                defaultMode);
        }
        currentMode = choices[0].ToAscii();
    } else {
        currentMode = defaultMode;
    }


    /*
     * Create the window if necessary.
     */
    if (mpPhoneWindow == NULL) {
        // create, setup, and then show window
        mpPhoneWindow = new PhoneWindow(this, mPhoneWindowPosn);
        mpPhoneWindow->SetCurrentMode((const char*)currentMode);
        if (!mpPhoneWindow->Setup(idx)) {
            delete mpPhoneWindow;
            mpPhoneWindow = NULL;
        }
        if (mpPhoneWindow != NULL) {
            mpPhoneWindow->Show();
            //mpPhoneWindow->CheckPlacement();
        }
    } else {
        // just set up for new device
        mpPhoneWindow->SetCurrentMode((const char*)currentMode);
        if (!mpPhoneWindow->Setup(idx)) {
            // it's in an uncertain state, blow it away
            delete mpPhoneWindow;
            mpPhoneWindow = NULL;
        }
    }

    /*
     * Reconfigure mode selection box.
     */
    wxComboBox* pModeSelection;
    pModeSelection = (wxComboBox*)FindWindow(IDC_MODE_SELECT);
    pModeSelection->Clear();
    for (i = 0; i < numChoices; i++)
        pModeSelection->Append(choices[i]);
    pModeSelection->SetSelection(0);
    pModeSelection->Enable(numChoices > 1);
    
    /*
     * configure qwerty keyboard attribute
     */
    numKeyboards = pPhoneData->GetNumKeyboards();
    if (numKeyboards > 0) {
        // only use the first keyboard for now
        PhoneKeyboard* pPhoneKeyboard;
        pPhoneKeyboard = pPhoneData->GetPhoneKeyboard(0);
        if (pPhoneKeyboard->getQwerty()) {
            printf("Sim: set 'qwerty' env\n");
            setenv("qwerty", "true", true);
        }
    }
    
bail:
    delete[] choices;
}

/*
 * Figure out which device is currently selected.
 *
 * The easiest way to do this is just run down the list of possible IDs
 * and stop when something claims to be checked.
 *
 * Returns -1 if it can't find a checked item (which can happen if no
 * device layouts were found).
 */
int MainFrame::GetSelectedDeviceIndex(void)
{
    wxMenuBar* pMenuBar;
    wxMenu* pMenu;
    int idx;
    
    pMenuBar = GetMenuBar();
    idx = pMenuBar->FindMenu(kDeviceMenuString);
    if (idx == wxNOT_FOUND) {
        fprintf(stderr, "Sim: couldn't find %s menu\n", (const char*) kDeviceMenuString.ToAscii());
        return -1;
    }

    pMenu = pMenuBar->GetMenu(idx);

    //printf("Menu.MenuItemCount = %d\n", pMenu->GetMenuItemCount());
    for (int j = pMenu->GetMenuItemCount() -1; j >= 0; j--) {
        wxMenuItem* pItem;

        pItem = pMenu->FindItemByPosition(j);
        //printf("ITEM %d: %s\n", j, (const char*) pItem->GetLabel());
        if (pItem->IsChecked()) {
            printf("Sim: selected device is '%s'\n",
                (const char*) pItem->GetLabel().ToAscii());
            return j;
        }
    }

    return -1;
}

/*
 * Receive a status message from the runtime thread.
 */
void MainFrame::OnUserEvent(UserEvent& event)
{
    UserEventMessage* pUem;

    pUem = (UserEventMessage*) event.GetData();
    assert(pUem != NULL);

    switch (pUem->GetType()) {
    case UserEventMessage::kRuntimeStarted:
        printf("Sim: runtime thread started!\n");
        HandleRuntimeStart();
        break;
    case UserEventMessage::kRuntimeStopped:
        printf("Sim: runtime thread stopped!\n");
        HandleRuntimeStop();
        break;
    case UserEventMessage::kErrorMessage:
        {
            wxString msg = pUem->GetString();
            wxMessageBox(msg, wxT("Android Runtime Error"),
                wxOK | wxICON_WARNING, this);
        }
        break;
    case UserEventMessage::kLogMessage:
        mpLogWindow->AddLogMessage(pUem->GetLogMessage());
        break;
    case UserEventMessage::kExternalRuntime:
        HandleExternalRuntime(pUem->GetReader(), pUem->GetWriter());
        break;
    default:
        printf("Sim: MESSAGE: unknown UserEventMessage rcvd (type=%d)\n",
            pUem->GetType());
        break;
    }

    delete pUem;
}

/*
 * The device management thread is up, so the runtime should be fully
 * running shortly.
 */
void MainFrame::HandleRuntimeStart(void)
{
    mSimRunning = true;

    SetStatusText(kStatusRunning, 1);
}

/*
 * The device management thread is exiting, so the runtime must be dead.
 */
void MainFrame::HandleRuntimeStop(void)
{
    mSimRunning = false;

    SetStatusText(kStatusNotRunning, 1);

    if (mRestartRequested) {
        printf("Sim: restarting runtime\n");
        mRestartRequested = false;
        SetupPhoneUI(GetSelectedDeviceIndex(), NULL);
        if (mpPhoneWindow != NULL)
            mpPhoneWindow->GetDeviceManager()->StartRuntime();
    }
}

/*
 * Handle a connection from an external runtime.
 */
void MainFrame::HandleExternalRuntime(android::Pipe* reader,
    android::Pipe* writer)
{
    android::MessageStream msgStream;
    android::Message msg;

    if (IsRuntimeRunning()) {
        /*
         * Tell the new guy to go away.
         */
        if (!msgStream.init(reader, writer, true)) {
            fprintf(stderr, "Sim: WARNING: unable to talk to remote runtime\n");
            goto bail;
        }

        printf("Sim: telling external runtime to go away\n");
        msg.setCommand(android::Simulator::kCommandGoAway, 0);
        msgStream.send(&msg);
    } else {
        printf("Sim: new external runtime wants to talk to us\n");

        /*
         * Launch the pieces necessary to talk to this guy.
         */
        int id = GetSelectedDeviceIndex();
        if (id < 0) {
            fprintf(stderr,
                "Sim: could not identify currently selected device\n");
            goto bail;
        }

        /* kill existing window, so it pops up and reclaims focus */
        if (mpPhoneWindow != NULL) {
            Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
            bool okay;

            if (pPrefs->GetBool("refocus-on-restart", &okay) && okay) {
                printf("Sim: inducing phone window refocus\n");
                mpPhoneWindow->Close(TRUE);     // no veto
                mpPhoneWindow = NULL;
            }
        }

        SetupPhoneUI(id, NULL);
        if (mpPhoneWindow != NULL) {
            mpPhoneWindow->GetDeviceManager()->StartRuntime(reader, writer);
        } else {
            fprintf(stderr, "Sim: ERROR: unable to get runtime going\n");
            goto bail;
        }

        // we don't own these anymore
        reader = writer = NULL;
    }

bail:
    delete reader;
    delete writer;
}

/*
 * The phone window is about to destroy itself.  Get rid of our pointer
 * to it, and record its last position so we can create the new one in
 * the same place.
 */
void MainFrame::PhoneWindowClosing(int x, int y)
{
    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();

    mpPhoneWindow = NULL;

    mPhoneWindowPosn.x = x;
    mPhoneWindowPosn.y = y;

    pPrefs->SetInt("window-device-x", x);
    pPrefs->SetInt("window-device-y", y);
}