diff options
Diffstat (limited to 'SrcShared/EmEventOutput.cpp')
-rw-r--r-- | SrcShared/EmEventOutput.cpp | 2012 |
1 files changed, 2012 insertions, 0 deletions
diff --git a/SrcShared/EmEventOutput.cpp b/SrcShared/EmEventOutput.cpp new file mode 100644 index 0000000..b173321 --- /dev/null +++ b/SrcShared/EmEventOutput.cpp @@ -0,0 +1,2012 @@ +/* -*- mode: C++; tab-width: 4 -*- */ +/* ===================================================================== *\ + Copyright (c) 2001 Palm, Inc. or its subsidiaries. + All rights reserved. + + This file is part of the Palm OS Emulator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +\* ===================================================================== */ + +#include "EmCommon.h" +#include "EmEventOutput.h" + +#include "EmMemory.h" // EmMem_strcpy +#include "EmPalmOS.h" // GenerateStackCrawl +#include "EmPalmStructs.h" // EmAliasControlType +#include "EmPatchState.h" // EmPatchState::GetCurrentAppInfo +#include "EmSession.h" // gSession +#include "ErrorHandling.h" // Errors::GetAppName, GetAppVersion +#include "Miscellaneous.h" // StackCrawlStrings +#include "PreferenceMgr.h" // Preference, gEmuPrefs +#include "ROMStubs.h" // FtrGet + + +// Special rank positions: + +#define kObjectRank_None -2 +#define kObjectRank_Last -1 + +// PalmOS table font: + +#define tableFont boldFont + +// WindowFlagsType modal flag bit: + +#define WindowTypeFlags_modal 0x2000 + +// Useful macros: + +static inline Bool PrvIsPenEvent (const EmRecordedEvent& event) +{ + return event.eType == kRecordedPenEvent; +} + +static inline Bool PrvIsPenUp (const PointType& pt) +{ + return pt.x == -1 && pt.y == -1; +} + +static inline Bool PrvIsPenDown (const PointType& pt) +{ + return !::PrvIsPenUp (pt); +} + +static inline Bool PrvIsPenUp (const EmRecordedEvent& event) +{ + return ::PrvIsPenEvent (event) && ::PrvIsPenUp (event.penEvent.coords); +} + +static inline Bool PrvIsPenDown (const EmRecordedEvent& event) +{ + return ::PrvIsPenEvent (event) && ::PrvIsPenDown (event.penEvent.coords); +} + + +#define POINT_IN_RECT(X,Y,R) (X >= R.topLeft.x && \ + Y >= R.topLeft.y && \ + X <= R.topLeft.x + R.extent.x && \ + Y <= R.topLeft.y + R.extent.y) + +#define IS_EXTENDED(chrcode) (chrcode >= 0x0080 && chrcode <= 0x00FF) + + +// Global state variables: + +EmEventInfoList EmEventOutput::fgEventInfo; +Bool EmEventOutput::fgIsGatheringInfo; +int EmEventOutput::fgCounter; +UInt16 EmEventOutput::fgPreviousFormID; +Bool EmEventOutput::fgEventAwaitingInfo; // Set if tapped on field or list; cleared when we get the awaited information. +Bool EmEventOutput::fgWaitForPenUp; + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::StartGatheringInfo + * + * DESCRIPTION: Reset our state in preparation to start gathering info. + * This should be called each time we start replaying an + * event list. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::StartGatheringInfo (void) +{ + fgEventInfo.clear (); + fgCounter = 0; + fgPreviousFormID = 0; + fgEventAwaitingInfo = false; + fgWaitForPenUp = false; + + EmEventOutput::GatherInfo (true); +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::IsGatheringInfo + * + * DESCRIPTION: Return whether or not we are currently gathering information. + * + * PARAMETERS: None. + * + * RETURNED: True if we are currently gathering info. + * + ***********************************************************************/ + +Bool EmEventOutput::IsGatheringInfo (void) +{ + return fgIsGatheringInfo; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::GatherInfo + * + * DESCRIPTION: Turn information gathering on or off. + * + * PARAMETERS: gatherInfo - whether or not we're turning on + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::GatherInfo (Bool gatherInfo) +{ + fgIsGatheringInfo = gatherInfo; +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: PrvGetStringFromEmuMemory + * + * DESCRIPTION: Retrieve a string from emulator memory and store it in + * a string object. + * + * PARAMETERS: emustr - pointer to the string in emulator memory + * + * RETURNED: The string object. + * + ***********************************************************************/ + +static string PrvGetStringFromEmuMemory (emuptr emustr) +{ + string str; + + if (!emustr) + return str; + + size_t len = EmMem_strlen (emustr); + + if (len) + { + char* temp = new char [len + 1]; + + if (temp == NULL) + { + // Failed to allocate space for temporary buffer. + return str; + } + + EmMem_strcpy (temp, emustr); + + str = temp; + + delete [] temp; + } + + return str; +} + + +/*********************************************************************** + * + * FUNCTION: PrvClearEventData + * + * DESCRIPTION: Clear out information gathered and stored in an event + * without disturbing the event itself. + * + * PARAMETERS: event - event to clear + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +static void PrvClearEventData (EmEventInfo& eventInfo) +{ + eventInfo.text.empty (); + eventInfo.newFormText.empty (); + eventInfo.stackCrawl.clear (); + + eventInfo.objKind = (FormObjectKind) -1; + eventInfo.objID = 0; + eventInfo.rank = kObjectRank_None; + eventInfo.row = -1; + eventInfo.column = -1; + eventInfo.style = (ControlStyleType) -1; + + eventInfo.asciiCode = 0; + eventInfo.keyCode = 0; + eventInfo.modifiers = 0; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::GetEventInfo + * + * DESCRIPTION: Master function for retrieving information from the + * emulator related to gremlin events being posted. + * + * PARAMETERS: event - the current event being replayed. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::GetEventInfo (const EmRecordedEvent& event) +{ + if (!fgIsGatheringInfo) + return; + + CEnableFullAccess munge; // Direct access to form.window.windowFlags.flags. + + // Clear current event data. + + EmEventInfo eventInfo; + ::PrvClearEventData (eventInfo); + + eventInfo.event = event; + + // Get the active form and form id. + // This may be incorrect for popup dialogs. + + FormType* frmP = ::FrmGetActiveForm (); + + UInt16 currentFormID = 0; + + if (frmP) + { + currentFormID = ::FrmGetFormId (frmP); + } + + // If the active form has changed since last event: + + if ((currentFormID != fgPreviousFormID) && currentFormID) + { + // Cancel the event awaiting info, because it is no longer possible to get info. + + fgEventAwaitingInfo = false; + + if (currentFormID) + { + // If there was a previous event, associate the form change with it. + + if (fgEventInfo.size () > 0) + { + EmEventInfo& prevEvent = fgEventInfo.back (); + + EmAliasFormType<PAS> form ((emuptr) frmP); + + // Get the new form's title. + + emuptr emuTitle = (emuptr)::FrmGetTitle (frmP); + + if (emuTitle) + { + string title = ::PrvGetStringFromEmuMemory (emuTitle); + + if (!title.empty ()) + { + if (form.window.windowFlags.flags & WindowTypeFlags_modal) + { + prevEvent.newFormText = string("open the \"") + title + string("\" dialog"); + } + else + { + prevEvent.newFormText = string("go to the \"") + title + string("\" view"); + } + } + } + + // If there is no title: + + if (prevEvent.newFormText.empty ()) + { + if (form.window.windowFlags.flags & WindowTypeFlags_modal) + { + prevEvent.newFormText = "go to a new dialog"; + } + else + { + prevEvent.newFormText += "go to a new view"; + } + } + } + } + } + + // A previous pen down event may have set the fgEventAwaitingInfo flag. + // This flag means that we want to scope out the state of the system + // later to find out what the pen event caused to happen. We gather + // that information on the next event after the ensuing pen up event. + // + // This will not work 100% of the time, because the event's target object + // may have disappeared, the active form may have changed, or the + // application may have done something to the object. + + if (fgEventAwaitingInfo) + { + // If we're waiting for a pen-up event, see if we found it. + + if (fgWaitForPenUp) + { + if (::PrvIsPenUp (eventInfo.event)) + { + fgWaitForPenUp = false; + } + } + else + { + EmEventOutput::GetAwaitedEventInfo (); + + fgEventAwaitingInfo = false; + } + } + + // Get information about current event. + // Skip pen up events. + + if (!::PrvIsPenUp (eventInfo.event)) + { + switch (eventInfo.event.eType) + { + case kRecordedPenEvent: + + EmEventOutput::GetPenEventInfo (eventInfo); + + break; + + case kRecordedAppSwitchEvent: + + EmEventOutput::GetAppSwitchEventInfo (eventInfo); + + break; + + default: + + break; + } + } + + fgPreviousFormID = currentFormID; + + // For some reason, I sometimes see *two* sets of instructions + // in the output -- one a duplicate of the other. However, they + // are all number consecutively, which implies that OutputEvents + // is called just once, and that the doubling effect is because + // two sets of events are in fgEventInfo. Put a check here to + // see if that's the case. + + if (fgEventInfo.size () > 0) + { + EmAssert (fgEventInfo.back ().event.eType != kRecordedErrorEvent); + } + + fgEventInfo.push_back (eventInfo); +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::PoppingUpForm + * + * DESCRIPTION: Called by the FrmPopupForm headpatch to tell us that + * the application is popping up a dialog. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::PoppingUpForm (void) +{ + if (!fgIsGatheringInfo) + return; + + // If there was a previous event, associate the form change with it. + + if (fgEventInfo.size () > 0) + { + fgEventInfo.back ().newFormText = "popup a dialog"; + } +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::ErrorOccurred + * + * DESCRIPTION: Called by Errors::HandleDialog to tell us the text of + * the error that occurred. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::ErrorOccurred (const string& msg) +{ + if (!fgIsGatheringInfo) + return; + + // Create a new event recording the information. + + EmEventInfo eventInfo; + + eventInfo.event.eType = kRecordedErrorEvent; + eventInfo.text = msg; + + // Capture the stack crawl. + + EmStackFrameList stackCrawl; + EmPalmOS::GenerateStackCrawl (stackCrawl); + + // Generate a full stack crawl. + + ::StackCrawlStrings (stackCrawl, eventInfo.stackCrawl); + + // Save this information at the end of our collection. + + fgEventInfo.push_back (eventInfo); +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: PrvObjectIsHigherRank + * + * DESCRIPTION: Determine if an object is ranked higher inside a form + * than another object. Ranking is top to bottom, then + * left to right. + * + * PARAMETERS: r1 - bounds of object in question + * + * r2 - bounds of object we're comparing to + * + * RETURNED: True if object (r1) is higher-ranked. + * + ***********************************************************************/ + +static Bool PrvObjectIsHigherRank (RectangleType& r1, RectangleType& r2) +{ + if (r2.topLeft.y < r1.topLeft.y) + return true; + + if (r2.topLeft.y == r1.topLeft.y) + { + if (r2.topLeft.x < r1.topLeft.x) + return true; + } + + return false; +} + + +/*********************************************************************** + * + * FUNCTION: PrvFindObjectRank + * + * DESCRIPTION: Find the rank number of an object inside a form. + * + * PARAMETERS: frmP - pointer to form containing the object + * + * objectIndices - vector array of object indexes in form + * + * object1 - index of the object to rank + * + * RETURNED: Rank value of the object. + * + ***********************************************************************/ + +static int16 PrvFindObjectRank (FormPtr frmP, vector<uint16>& objectIndices, uint16 object1) +{ + EmAssert (frmP); + + RectangleType r1; + ::FrmGetObjectBounds (frmP, object1, &r1); + + FormObjectKind objectKind1 = ::FrmGetObjectType (frmP, object1); + + int rank = 0; + uint16 siblingCount = 0; + + vector<uint16>::iterator iter = objectIndices.begin (); + + while (iter != objectIndices.end ()) + { + // Make sure object is not ourself + + if (*iter != object1) + { + // Make sure object is the same kind + + FormObjectKind objectKind2 = ::FrmGetObjectType (frmP, *iter); + + if (objectKind2 == objectKind1) + { + siblingCount++; + + RectangleType r2; + ::FrmGetObjectBounds (frmP, *iter, &r2); + + if (::PrvObjectIsHigherRank (r1, r2)) + rank++; + } + } + + ++iter; + } + + // No ranking if we are all alone. + + if (siblingCount == 0) + return kObjectRank_None; + + // Special case for the last object in the form. + + else if (rank == siblingCount) + return kObjectRank_Last; + + // Else just use the rank number. + + return rank; +} + + +/*********************************************************************** + * + * FUNCTION: PrvPointInTableItem + * + * DESCRIPTION: This routine computes which row, column a point is in. + * It was adapted from the function 'PointInTableItem' in + * UI/src/Table.cpp by art and peter. + * + * PARAMETERS: tableP - pointer to a table object + * + * x - point's x coordinate + * + * y - point's y coordinate + * + * rrow - return the row + * + * rcol - return the column + * + * RETURNED: True if the point is in at item. + * + ***********************************************************************/ + +static Boolean PrvPointInTableItem (TableType* tableP, Coord x, Coord y, Int16& rrow, Int16& rcol) +{ + EmAssert (tableP); + + CEnableFullAccess munge; + + Int16 row, col; + Int16 numRows, numCols; + RectangleType r; + + EmAliasTableType<PAS> table ((emuptr)tableP); + + RectangleType bounds; + bounds.topLeft.x = table.bounds.topLeft.x; + bounds.topLeft.y = table.bounds.topLeft.y; + bounds.extent.x = table.bounds.extent.x; + bounds.extent.y = table.bounds.extent.y; + + if (!POINT_IN_RECT(x, y, bounds)) + return false; + + r.topLeft.x = table.bounds.topLeft.x; + r.topLeft.y = table.bounds.topLeft.y; + + numRows = table.numRows; + for (row = table.topRow; row < numRows; row++) + { + // Is the point within the bounds of the row. + + r.extent.x = table.bounds.extent.x; + r.extent.y = ::TblGetRowHeight (tableP, row); + + if (POINT_IN_RECT(x, y, r)) + { + numCols = table.numColumns; + for (col = 0; col < numCols; col++) + { + // Is the point within the bounds of the column + + r.extent.x = ::TblGetColumnWidth (tableP, col); + r.extent.y = ::TblGetRowHeight (tableP, row); + + if (POINT_IN_RECT(x, y, r)) + { + rrow = row; + rcol = col; + return true; + } + + // Move to the next column. + + r.topLeft.x += r.extent.x + ::TblGetColumnSpacing (tableP, col); + } + + // We were in a useable row, but we were not in any of the columns. + + return false; + } + + // Move to next row. + + r.topLeft.x = table.bounds.topLeft.x; + r.topLeft.y += ::TblGetRowHeight (tableP, row); + } + + return false; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::GetPenEventInfo + * + * DESCRIPTION: Get information about the current pen event. EventAwaitingInfo + * is set for events that need to retrieve info a little later. + * + * PARAMETERS: event - the current event being replayed + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::GetPenEventInfo (EmEventInfo& eventInfo) +{ + EmAssert (eventInfo.event.eType == kRecordedPenEvent); + EmAssert (fgIsGatheringInfo); + + CEnableFullAccess munge; + + // Get the pen location in display coordinates. + + EmPoint displayPt (eventInfo.event.penEvent.coords); + Int16 displayX = displayPt.fX; + Int16 displayY = displayPt.fY; + + EmPoint localPt = displayPt; + Int16 localX = displayX; + Int16 localY = displayY; + + // Get the active form. + + FormPtr frmP = ::FrmGetActiveForm (); + + // Get the objects in that form. + + vector<uint16> objectIndices; + ::CollectOKObjects (frmP, objectIndices); + + // Convert the pen location to local form coordinates. + + if (frmP) + { + WinHandle oldDrawWin = ::WinSetDrawWindow (::FrmGetWindowHandle (frmP)); + ::WinDisplayToWindowPt (&localX, &localY); + ::WinSetDrawWindow (oldDrawWin); + + localPt = EmPoint (localX, localY); + } + + // Iterate over the objects, finding one we hit. + + vector<uint16>::iterator iter = objectIndices.begin (); + + while (iter != objectIndices.end ()) + { + RectangleType r; + ::FrmGetObjectBounds (frmP, *iter, &r); + + EmRect bounds (r); + + // Must expand bounds a little to match what PalmOS detects as tapping inside an object. + + bounds.fBottom += 2; + bounds.fRight += 2; + + if (bounds.Contains (localPt)) + { + break; + } + + ++iter; + }; + + // If we found a hit on a form object... + + if (iter != objectIndices.end ()) + { + eventInfo.objID = *iter; + + // Get its type. + + eventInfo.objKind = ::FrmGetObjectType (frmP, *iter); + + // Get its rank. + + eventInfo.rank = ::PrvFindObjectRank (frmP, objectIndices, *iter); + + // Get special info for object type. + + switch (eventInfo.objKind) + { + case frmFieldObj: + { + // Set up to retrieve info during the next event. + + eventInfo.text.empty (); + fgEventAwaitingInfo = true; + fgWaitForPenUp = false; + + break; + } + + case frmControlObj: + { + emuptr ctlP = (emuptr) ::FrmGetObjectPtr (frmP, *iter); + + if (ctlP) + { + EmAliasControlType<PAS> control (ctlP); + + // Store control style. + + eventInfo.style = control.style; + + // Store text label if it exists. + + eventInfo.text = ::PrvGetStringFromEmuMemory (control.text); + + if (!eventInfo.text.empty ()) + { + // Check if label is unique. If so, no need for rank. + + eventInfo.rank = kObjectRank_None; + + // TODO: Else rank it amongst peers with same label. + } + } + + break; + } + + case frmListObj: + { + // Set up to retrieve info during the next event. + + eventInfo.text.empty (); + eventInfo.row = -1; + fgEventAwaitingInfo = true; + fgWaitForPenUp = true; + + break; + } + + case frmTableObj: + { + TableType* tableP = (TableType*)::FrmGetObjectPtr (frmP, *iter); + + if (tableP) + { + ::PrvPointInTableItem (tableP, localX, localY, + eventInfo.row, eventInfo.column); + } + + break; + } + + case frmLabelObj: + +// my ($label_id) = FrmGetObjectId ($form, $ii); +// my ($address, $label) = FrmGetLabel($form, $label_id); +// $line .= " \"$label\""; + +// event.text = "labeltext"; + break; + + case frmTitleObj: + +// my ($address, $title) = FrmGetTitle($form,); +// $line .= " \"$title\""; + +// event.text = "titletext"; + break; + + default: + break; // Handle all other cases to keep gcc happy. + } + } + + // Otherwise, see if we hit in the silkscreen area. + + else + { + UInt16 numButtons; + const PenBtnInfoType* buttonListP = EvtGetPenBtnList(&numButtons); + EmAliasPenBtnInfoType<PAS> buttonList ((emuptr) buttonListP); + + for (UInt16 buttonIndex = 0; buttonIndex < numButtons; ++buttonIndex) + { + EmAliasPenBtnInfoType<PAS> button = buttonList[buttonIndex]; + + RectangleType buttonBounds; + buttonBounds.topLeft.x = button.boundsR.topLeft.x; + buttonBounds.topLeft.y = button.boundsR.topLeft.y; + buttonBounds.extent.x = button.boundsR.extent.x; + buttonBounds.extent.y = button.boundsR.extent.y; + + if (POINT_IN_RECT(displayX, displayY, buttonBounds)) + { + eventInfo.asciiCode = button.asciiCode; + eventInfo.keyCode = button.keyCode; + eventInfo.modifiers = button.modifiers; + break; + } + } + } +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::GetAppSwitchEventInfo + * + * DESCRIPTION: Get information about the current app switch event. + * In particular, any recorded "from application" + * information recorded in it is probably incorrect now, + * so let's generate some new information. + * + * PARAMETERS: event - the current event being replayed + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::GetAppSwitchEventInfo (EmEventInfo& eventInfo) +{ + EmuAppInfo currentApp = EmPatchState::GetCurrentAppInfo (); + + eventInfo.event.appSwitchEvent.oldCardNo = currentApp.fCardNo; + eventInfo.event.appSwitchEvent.oldDbID = currentApp.fDBID; +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: PrvFindInsertionPointString + * + * DESCRIPTION: Create a string that identifies where a field was tapped, + * using the position insertion point. Currently we just + * copy the contents of the field up to the insertion point. + * + * PARAMETERS: emustr - contents of the field in emulator memory + * + * pos - position of the insertion point + * + * RETURNED: String identifying where the field was tapped. + * + ***********************************************************************/ + +static string PrvFindInsertionPointString (emuptr emustr, UInt16 pos) +{ + EmAssert (emustr); + + // Copy the text before pos from emulator memory into local temp buffer. + + char* temp = new char [pos + 1]; + if (temp == NULL) + { + // Failed to create temp buffer. + return string (); + } + + ::EmMem_strncpy (temp, emustr, (size_t) pos); + + // Perhaps.. Scan backwards from pos until string up to pos is unique inside entire field string. + // For now we'll just use the entire string up to pos. + + temp[pos] = 0; + + // Return result. + + string finalString = temp; + + delete [] temp; + + return finalString; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::GetAwaitedEventInfo + * + * DESCRIPTION: Master function to get information from the emulator about + * an event that has been waiting for information. This + * mechanism is used to give the running application or OS + * enough time to respond to a gremlin event before we try to + * get information about it. This is used for finding where we + * tapped inside a field, a list, or a table. After the gremlin + * taps inside the target object, FldHandleEvent, LstHandleEvent, + * or TblHandleEvent will compute the new insertion point or + * selection due to that pen event. We then try to retrieve the + * results of that computation. This method won't work if the + * application changes the target object before we have a chance + * to retrieve its info, or the object gets destroyed. + * + * PARAMETERS: None + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::GetAwaitedEventInfo (void) +{ + EmAssert (fgEventAwaitingInfo); + EmAssert (fgIsGatheringInfo); + + CEnableFullAccess munge; // Remove blocks on memory access. + + // It is entirely possible that the active form has been destroyed or changed. + + FormType* frmP = ::FrmGetActiveForm (); + + if (frmP == NULL) + { + return; + } + + // Look for an event that needs completing. + + Bool foundAwaitedEvent = false; + EmEventInfoList::reverse_iterator iter = fgEventInfo.rbegin (); + + while (!foundAwaitedEvent && iter != fgEventInfo.rend ()) + { + EmEventInfo& awaitingEvent = *iter++; + + switch (awaitingEvent.event.eType) + { + case kRecordedPenEvent: + { + switch (awaitingEvent.objKind) + { + case frmFieldObj: + { + // Find the field text to tap after. + + FieldType* fldP = (FieldType*) ::FrmGetObjectPtr (frmP, awaitingEvent.objID); + + if (fldP) + { + emuptr txtP = (emuptr) ::FldGetTextPtr (fldP); + + if (txtP) + { + UInt16 pos = ::FldGetInsPtPosition (fldP); + + if (pos == 0) + { + // Tap at the beginning. + + awaitingEvent.text = " at the beginning"; + } + else + { + // Tap after the text 'blah'. + + string aftertext = + ::PrvFindInsertionPointString (txtP, pos); + + awaitingEvent.text = " after the text \"" + aftertext + "\""; + } + } + } + + foundAwaitedEvent = true; + + break; + } // case frmFieldObj + + case frmListObj: + { + // Find the list item to tap on. + + ListType* lstP = (ListType*)::FrmGetObjectPtr (frmP, awaitingEvent.objID); + + if (lstP) + { + Int16 currentItem = ::LstGetSelection (lstP); + + // If there is a selection: + + if (currentItem != noListSelection) + { + // Use text if it exists, else use row number. + + emuptr textP = (emuptr) ::LstGetSelectionText (lstP, currentItem); + + if (textP && ::EmMem_strlen(textP) > 0) + { + // This may not be unique... + awaitingEvent.text = ::PrvGetStringFromEmuMemory (textP); + } + else + { + // Should this be relative to topItem? + awaitingEvent.row = currentItem; + } + } + } + + foundAwaitedEvent = true; + + break; + } // case frmListObj + + default: + { + // fgEventAwaitingInfo set inappropriately + + EmAssert (false); + } + } // switch (objKind) + + break; + } // case pen event + + default: + { + // fgEventAwaitingInfo set inappropriately + + EmAssert (false); + } + } // switch (eType) + } // while +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputEvents + * + * DESCRIPTION: Master output function - write out a text description of + * a gremlin event list. + * + * PARAMETERS: events - list of events to output + * + * stream - string stream to write output into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputEvents (strstream& stream) +{ + // Start step counter from one. The current step number is not necessarily + // the same as the current event index, because multiple events can go into + // a single step. + + fgCounter = 1; + + // Write out starting step. + + EmEventOutput::OutputStartStep (stream); + + // Iterate over all the events. + + EmEventInfoList::iterator iter (fgEventInfo.begin ()); + + while (iter != fgEventInfo.end ()) + { + switch (iter->event.eType) + { + case kRecordedKeyEvent: + + EmEventOutput::OutputKeyEventStep (iter, stream); + break; + + case kRecordedPenEvent: + + EmEventOutput::OutputPenEventStep (*iter, stream); + break; + + case kRecordedAppSwitchEvent: + + EmEventOutput::OutputAppSwitchEventStep (*iter, stream); + break; + + case kRecordedErrorEvent: + + EmEventOutput::OutputErrorEvent (*iter, stream); + break; + + case kRecordedNullEvent: + + // No output for null events. + break; + + default: + + stream << "Unknown event" << endl; + EmAssert (false); + } + + ++iter; + } +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputStartStep + * + * DESCRIPTION: Write out the description of the starting step, + * ie launching the application being exercised. + * + * PARAMETERS: stream - the output stream to write into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputStartStep (strstream& stream) +{ + stream << endl; + stream << fgCounter << ". "; + + // Get and write out application name and version. + + string appNameUC; + string appNameLC; + string appVersion; + + Errors::GetAppName (appNameUC, appNameLC); + Errors::GetAppVersion (appVersion); + + stream << "Start \"" << appNameLC << "\" " << appVersion << ""; + + // Device name: + + Preference<Configuration> pref (kPrefKeyLastConfiguration); + + Configuration cfg = *pref; + EmDevice device = cfg.fDevice; + string deviceStr = device.GetMenuString (); + + stream << " on a " << deviceStr; + + // ROM version: + + UInt32 romVersionData; + ::FtrGet (sysFileCSystem, sysFtrNumROMVersion, &romVersionData); + + UInt32 romVersionMajor = sysGetROMVerMajor (romVersionData); + UInt32 romVersionMinor = sysGetROMVerMinor (romVersionData); + + stream + << " running Palm OS " + << romVersionMajor << "." << romVersionMinor + << "." << endl; + + fgCounter++; +} + + +#pragma mark - + +enum KeyType +{ + kKeyType_Writeable, // a normal character that can be entered by typing/graffiti + kKeyType_Button, // a button + kKeyType_Event // an event, not necessarily from user input +}; + + +struct KeyDescription +{ + char * description; + uint16 chrcode; + KeyType type; +}; + + +// List of text descriptions for different kinds of character codes. +// Palm OS chr and vchr defines are in Core/System/Chars.h. +// Items followed by a '-' are not currently generated by gremlins. + +static KeyDescription gKeyDescription [] = +{ + { "backspace", chrBackspace, kKeyType_Writeable }, // 0x0008 + { "tab", chrHorizontalTabulation, kKeyType_Writeable }, // 0x0009 + { "new line", chrLineFeed, kKeyType_Writeable }, // 0x000A + { "page up", vchrPageUp, kKeyType_Button }, // 0x000B + { "page down", vchrPageDown, kKeyType_Button }, // 0x000C + { "carriage return", chrCarriageReturn, kKeyType_Writeable }, // 0x000D + { "escape", chrEscape, kKeyType_Writeable }, // 0x001B - + { "left arrow", chrLeftArrow, kKeyType_Button }, // 0x001C + { "right arrow", chrRightArrow, kKeyType_Button }, // 0x001D + { "up arrow", chrUpArrow, kKeyType_Button }, // 0x001E + { "down arrow", chrDownArrow, kKeyType_Button }, // 0x001F +// { " ", chrSpace, kKeyType_Writeable }, // 0x0020 + + // Everything here (0x0021 to 0x007E) is printable. + + { "<delete>", chrDelete, kKeyType_Writeable }, // 0x007F + + // extended characters from 0x0080 to 0x00FF + + // Remaining are either commands or wide characters. + + { "low battery", vchrLowBattery, kKeyType_Event }, // 0x0101 + { "next field", vchrNextField, kKeyType_Button }, // 0x0103 + { "Menu", vchrMenu, kKeyType_Button }, // 0x0105 + { "command", vchrCommand, kKeyType_Button }, // 0x0106 + { "Home", vchrLaunch, kKeyType_Button }, // 0x0108 + { "Keyboard", vchrKeyboard, kKeyType_Button }, // 0x0109 + { "Find", vchrFind, kKeyType_Button }, // 0x010A + { "Calculator", vchrCalc, kKeyType_Button }, // 0x010B - + { "prev field", vchrPrevField, kKeyType_Button }, // 0x010C + + // Currently, nothing beyond here is generated by gremlins. + + { "Keyboard \"abc\"", vchrKeyboardAlpha, kKeyType_Button }, // 0x0110 + { "Keyboard \"123\"", vchrKeyboardNumeric, kKeyType_Button }, // 0x0111 + { "lock", vchrLock, kKeyType_Button }, // 0x0112 + { "Backlight", vchrBacklight, kKeyType_Button }, // 0x0113 + { "auto off", vchrAutoOff, kKeyType_Event }, // 0x0114 - This one is currently disabled. + + // PalmOS 3.0 + + { "exgtest", vchrExgTest, kKeyType_Event }, // 0x0115 + { "send data", vchrSendData, kKeyType_Event }, // 0x0116 + { "ir receive", vchrIrReceive, kKeyType_Event }, // 0x0117 + + // PalmOS 3.1 + + { "tsm1", vchrTsm1, kKeyType_Event }, // 0x0118 + { "tsm2", vchrTsm2, kKeyType_Event }, // 0x0119 + { "tsm3", vchrTsm3, kKeyType_Event }, // 0x011A + { "tsm4", vchrTsm4, kKeyType_Event }, // 0x011B + + // PalmOS 3.2 + + { "radio coverage OK", vchrRadioCoverageOK, kKeyType_Event }, // 0x011C + { "radio coverage fail", vchrRadioCoverageFail, kKeyType_Event }, // 0x011D + { "power off", vchrPowerOff, kKeyType_Event }, // 0x011E + + // PalmOS 3.5 + + { "resume sleep", vchrResumeSleep, kKeyType_Event }, // 0x011F + + { "late wakeup", vchrLateWakeup, kKeyType_Event }, // 0x0120 + + { "tsm mode", vchrTsmMode, kKeyType_Event }, // 0x0121 + { "Brightness", vchrBrightness, kKeyType_Button }, // 0x0122 + { "Contrast", vchrContrast, kKeyType_Button }, // 0x0123 + { "exg int data", vchrExgIntData, kKeyType_Event }, // 0x01FF + + { "hard 1 (Date Book)", vchrHard1, kKeyType_Button }, // 0x0204 + { "hard 2 (Address Book)", vchrHard2, kKeyType_Button }, // 0x0205 + { "hard 3 (ToDo List)", vchrHard3, kKeyType_Button }, // 0x0206 + { "hard 4 (Note Pad)", vchrHard4, kKeyType_Button }, // 0x0207 + { "hard power", vchrHardPower, kKeyType_Button }, // 0x0208 + { "hard cradle", vchrHardCradle, kKeyType_Button }, // 0x0209 + { "hard cradle 2", vchrHardCradle2, kKeyType_Button }, // 0x020A + { "hard contrast", vchrHardContrast, kKeyType_Button }, // 0x020B + { "hard antenna", vchrHardAntenna, kKeyType_Event }, // 0x020C + { "hard brightness", vchrHardBrightness, kKeyType_Button }, // 0x020D + + { NULL, 0, (KeyType) 0 } +}; + + +/*********************************************************************** + * + * FUNCTION: PrvFindKeyDescription + * + * DESCRIPTION: Search for the text description of a character code + * recieved in event.data.keyDown.ascii. + * + * PARAMETERS: chrcode - the character code value to search for + * + * RETURNED: pointer to a KeyDescription structure that describes + * the character code, or null if no description found. + * + ***********************************************************************/ + +static KeyDescription* PrvFindKeyDescription (uint16 chrcode) +{ + // Iterate over the key description list and find an element with + // the same key code. + + for (KeyDescription* kd = gKeyDescription; kd->description != NULL; kd++) + { + if (kd->chrcode == chrcode) + return kd; + } + + // Failed to find description. + + return NULL; +} + + +/*********************************************************************** + * + * FUNCTION: PrvIsKeyWriteable + * + * DESCRIPTION: Determine whether a key event is writeable or not. By + * 'writeable' we mean that the key event can be reproduced + * by a user by typing or graffiti, as in a normal printable + * text character. This routine is used to determine whether + * multiple key events can be merged together into a single + * string. + * + * PARAMETERS: kd - the key description structure for this event, if it exists + * + * chrcode - the character code, ie from event.data.keyDown.ascii + * + * modifiers - the modifiers for this event + * + * RETURNED: True if the key event is writeable. + * + ***********************************************************************/ + +static Bool PrvIsKeyWriteable (KeyDescription* kd, uint16 chrcode, uint16 modifiers) +{ + // If commandKeyMask is set, can't be writeable. + + if (modifiers & commandKeyMask) + return false; + + // Everything above 0x0100 without commandKeyMask set is a writeable wide character..? + + if (chrcode >= 0x100) + return true; + + // Remaining values are below 0x100 without commandKeyMask set. + // If the key has a description, use it to determine writeability. + + if (kd) + { + return kd->type == kKeyType_Writeable; + } + + // If the key's character code is printable, we'll assume it's writeable. + + if (isprint (chrcode)) + return true; + + // If the key's character code is in the extended ascii range, we'll assume it's + // writeable. Problem with this is that it's not easy to enter an extended character + // into a Palm device.. + + if (IS_EXTENDED(chrcode)) + return true; + + // Anything else (meaning values below 0x0080 without commandKeyMask set and + // no key description specified) assumed to be unwriteable. + + return false; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputKeyEventStep + * + * DESCRIPTION: Write out the description of a key event. This routine + * handles key events that look like button presses or special + * event notifications (like LowBattery), as well as writeable + * characters. Successive writeable characters are merged + * together into a single string if possible. + * + * PARAMETERS: events - the entire event list + * + * iter - an iterator over the events list, currently + * located at a key event + * + * stream - the output stream to write into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputKeyEventStep (EmEventInfoList::iterator& eventInfoIter, strstream& stream) +{ + EmAssert (eventInfoIter->event.eType == kRecordedKeyEvent); + + stream << fgCounter << ". "; + + KeyDescription* kd = ::PrvFindKeyDescription (eventInfoIter->event.keyEvent.ascii); + + // If this key event is writeable, then build up a string + // of characters to write. + + if (::PrvIsKeyWriteable (kd, eventInfoIter->event.keyEvent.ascii, eventInfoIter->event.keyEvent.modifiers)) + { + string charString; + + // Scan forward until we find an event that doesn't involve writing, + // or we hit the end of the list. + + do + { + // Add character onto string: Use description if available, else just + // use the character itself. + + if (kd) + { + charString += string ("<") + kd->description + string (">"); + } + + else if (IS_EXTENDED (eventInfoIter->event.keyEvent.ascii)) + { + // Put brackets around extended character number + + char temp[16]; + sprintf (temp, "<%3d>", (int) eventInfoIter->event.keyEvent.ascii); + charString += temp; + } + + else + { + charString += eventInfoIter->event.keyEvent.ascii; + } + + ++eventInfoIter; + + // Stop if this event is not a key event. + + if (eventInfoIter->event.eType != kRecordedKeyEvent) + break; + + // Stop if this key event is not writeable. + + kd = ::PrvFindKeyDescription (eventInfoIter->event.keyEvent.ascii); + + if (!::PrvIsKeyWriteable (kd, eventInfoIter->event.keyEvent.ascii, eventInfoIter->event.keyEvent.modifiers)) + break; + + } while (eventInfoIter != fgEventInfo.end ()); + + // Undigest the event that caused us to break. + + --eventInfoIter; + + // Output the string. + + stream << "Write \"" << charString << "\"." << endl; + } + + else + { + if (kd) + { + // Check if it's a button. + + if (kd->type == kKeyType_Button) + { + stream << "Tap the " << kd->description << " button." << endl; + } + + // Check if it's an event. + + else if (kd->type == kKeyType_Event) + { + stream + << "Application receives a " + << kd->description << " event." << endl; + } + } + + else + { + stream + << "Write character " + << eventInfoIter->event.keyEvent.ascii << "." << endl; + } + } + + fgCounter++; +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: PrvGetRankString + * + * DESCRIPTION: Return a text string to describe a rank number. + * + * PARAMETERS: rank - the rank number to describe + * + * RETURNED: String containing the rank description. + * + ***********************************************************************/ + +static string PrvGetRankString (int rank) +{ + string rankString; + + // Special cases: + + if (rank == kObjectRank_Last) rankString = "last"; + else if (rank == 0) rankString = "first"; + else if (rank == 1) rankString = "second"; + else if (rank == 2) rankString = "third"; + + else + { + // Just use 'nth'. + + char temp[16]; + sprintf (temp, "%d", rank); + rankString = temp + string("th"); + } + + return rankString; +} + + +/*********************************************************************** + * + * FUNCTION: PrvGetObjectKindString + * + * DESCRIPTION: Return a string describing the type of a form object. + * + * PARAMETERS: objKind - the type of the object + * + * style - the style (for control objects) + * + * RETURNED: A string describing the object's type. + * + ***********************************************************************/ + +static string PrvGetObjectKindString (FormObjectKind objKind, ControlStyleType style) +{ + string kindString; + + switch (objKind) + { + case frmFieldObj: + kindString = "field"; + break; + + case frmControlObj: + + switch (style) + { + case buttonCtl: + kindString = "button"; + break; + + case pushButtonCtl: + kindString = "pushbutton"; + break; + + case checkboxCtl: + kindString = "checkbox"; + break; + + case popupTriggerCtl: + kindString = "popup trigger"; + break; + + case selectorTriggerCtl: + kindString = "selector trigger"; + break; + + case repeatingButtonCtl: + kindString = "repeating button"; + break; + + case sliderCtl: + kindString = "slider"; + break; + + case feedbackSliderCtl: + kindString = "feedback slider"; + break; + + default: + kindString = "control"; + } + + break; + + case frmListObj: + kindString = "list"; + break; + + case frmTableObj: + kindString = "table"; + break; + + case frmBitmapObj: + kindString = "bitmap"; + break; + +// case frmLineObj: +// case frmFrameObj: +// case frmRectangleObj: + + case frmLabelObj: + kindString = "label"; + break; + + case frmTitleObj: + kindString = "title"; + break; + + case frmPopupObj: + kindString = "popup"; + break; + + case frmGraffitiStateObj: + kindString = "graffiti shift indicator"; + break; + + case frmGadgetObj: + kindString = "gadget"; + break; + + case frmScrollBarObj: + kindString = "scrollbar"; + break; + + default: + kindString = "(unknown object type)"; + } + + return kindString; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputPenEventStep + * + * DESCRIPTION: Write out the description of a pen event. 'Lift stylus' + * events are ignored for now. + * + * PARAMETERS: event - the pen event to describe + * + * stream - the output stream to write into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputPenEventStep (const EmEventInfo& eventInfo, strstream& stream) +{ + EmAssert (eventInfo.event.eType == kRecordedPenEvent); + + PointType pen = eventInfo.event.penEvent.coords; + string text = eventInfo.text; + FormObjectKind kind = eventInfo.objKind; + Int16 rank = eventInfo.rank; + + if (::PrvIsPenUp (pen)) + { + // Skip lift stylus events. + // stream << "Lift stylus." << endl; + } + else + { + stream << fgCounter << ". "; + + // Tap on silkscreen + + KeyDescription* kd; + if (eventInfo.asciiCode != 0 && + (kd = ::PrvFindKeyDescription (eventInfo.asciiCode)) != NULL) + { + // Check if it's a button. + + if (kd->type == kKeyType_Button) + { + stream << "Tap the " << kd->description << " button"; + } + + // Check if it's an event. + + else if (kd->type == kKeyType_Event) + { + stream << "Application receives a " << kd->description << " event"; + } + } + + // Tap somewhere. + + else if (kind == -1) + { + // Just output location to tap at. + + stream << "Tap at " << pen.x << ", " << pen.y; + } + else + { + // Command. + + if ( (kind == frmFieldObj) + || (kind == frmTitleObj) + || (kind == frmTableObj)) + { + stream << "Tap in the "; + } + else + { + stream << "Tap the "; + } + + // Ranking. + + Bool needRankPostfix = false; + + if (rank == kObjectRank_None) + { + // Output control label or nothing. + + if (!text.empty () && (kind == frmControlObj)) + { + stream << "\"" << text << "\" "; + } + } + else + { + // Output ranking. + + stream << ::PrvGetRankString (rank) << " "; + + if (rank > 1) + needRankPostfix = true; + } + + // Object kind. + // Skip this for the special 'nth list item' case. + + if (!(kind == frmListObj && + rank == kObjectRank_None && + text.empty() && + eventInfo.row != -1)) + { + stream + << ::PrvGetObjectKindString (kind, eventInfo.style); + } + + // Remainder of ranking. + + if (needRankPostfix) + stream << " from the top"; + + // Special object data. + + switch (kind) + { + case frmFieldObj: + + if (!text.empty ()) + { + // After the text 'blah'. + + stream << text; + } + else + { + // Shouldn't need this because if event.text is empty then + // the field is empty so it doesn't matter where you tap. + // stream << " at " << pen.x << ", " << pen.y; + } + + break; + + case frmListObj: + + // Output item text if we have it, else try the item number. + + if (!text.empty ()) + { + if (rank == kObjectRank_None) + { + // Special case for forms with only one list. + + stream << " item \"" << text << "\""; + } + else + { + stream << ", in the \"" << text << "\" item"; + } + } + else if (eventInfo.row != -1) + { + if (rank == kObjectRank_None) + { + // Special case for forms with only one list. + + stream << ::PrvGetRankString (eventInfo.row) << " list item"; + } + else + { + stream << ", in the " << ::PrvGetRankString (eventInfo.row) << " item"; + } + } + else + { + stream << " at " << pen.x << ", " << pen.y; + } + + break; + + case frmTableObj: + + // Output column, row if we have it, else use x, y pixel position. + + if (eventInfo.row != -1) + { + stream + << " at column " << eventInfo.column + << " row " << eventInfo.row; + } + else + { + stream << " at " << pen.x << ", " << pen.y; + } + + break; + + default: + break; // Handle all other cases to keep gcc happy. + } + } + + // Form change. + + if (!eventInfo.newFormText.empty ()) + { + // "To go to the 'A' dialog" or "To open the 'B' view". + + stream << " to " << eventInfo.newFormText; + } + + stream << "." << endl; + + fgCounter++; + } +} + + +#pragma mark - + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputAppSwitchEventStep + * + * DESCRIPTION: Write out the description of an appSwitch event. + * + * PARAMETERS: event - the appSwitch event to describe + * + * stream - the output stream to write into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputAppSwitchEventStep (const EmEventInfo& eventInfo, strstream& stream) +{ + EmAssert (eventInfo.event.eType == kRecordedAppSwitchEvent); + + stream << fgCounter << ". "; + + uint16 cardNo = eventInfo.event.appSwitchEvent.cardNo; + uint32 dbID = eventInfo.event.appSwitchEvent.dbID; + uint16 oldCardNo = eventInfo.event.appSwitchEvent.oldCardNo; + uint32 oldDbID = eventInfo.event.appSwitchEvent.oldDbID; + + char name [dmDBNameLength] = { 0 }; + char oldName [dmDBNameLength] = { 0 }; + uint16 version; + uint16 oldVersion; + + #define GET_NAME(x, y) x, NULL, y, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL + + ::DmDatabaseInfo (cardNo, dbID, GET_NAME(name, &version)); + ::DmDatabaseInfo (oldCardNo, oldDbID, GET_NAME(oldName, &oldVersion)); + + if (strcmp (name, oldName) != 0) + { + stream << "Switch to \"" << name << "\" from \"" << oldName << "\"." << endl; + } + else + { + stream << "Relaunch \"" << name << "\"." << endl; + } + + fgCounter++; +} + + +/*********************************************************************** + * + * FUNCTION: EmEventOutput::OutputErrorEvent + * + * DESCRIPTION: Write out the description of an error event. + * + * PARAMETERS: event - the error event to describe + * + * stream - the output stream to write into + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void EmEventOutput::OutputErrorEvent (const EmEventInfo& eventInfo, strstream& stream) +{ + EmAssert (eventInfo.event.eType == kRecordedErrorEvent); + + // Output error description. Do this a character at + // a time in order to convert line endings. + + stream << endl; + stream << "Crash with error:" << endl << endl; + + { + string::const_iterator iter = eventInfo.text.begin (); + while (iter != eventInfo.text.end ()) + { + if (*iter == '\r' || *iter == '\n') + { + stream << endl; + } + else + { + stream << *iter; + } + + ++iter; + } + + stream << endl << endl; + } + + // Output the stack crawl. + + stream << "Function call stack:" << endl; + + EmEventOutput::OutputStack (stream, eventInfo.stackCrawl); + + stream << endl; +} + + +void EmEventOutput::OutputStack (strstream& stream, const StringList& stackCrawl) +{ + StringList::const_iterator iter = stackCrawl.begin (); + + while (iter != stackCrawl.end ()) + { + stream << "\t" << *iter << endl; + ++iter; + } +} |