// Parse a string argument. ASN1 strings can include nulls as valid characters. void MHParseBinary::ParseString(int endStr, MHOctetString &str) { // TODO: Don't deal with indefinite length at the moment. if (endStr == INDEFINITE_LENGTH) { MHERROR("Indefinite length strings are not implemented"); } int nLength = endStr - m_p; unsigned char *stringValue = (unsigned char *)malloc(nLength + 1); if (stringValue == NULL) { MHERROR("Out of memory"); } unsigned char *p = stringValue; while (m_p < endStr) { *p++ = GetNextChar(); } str.Copy(MHOctetString((const char *)stringValue, nLength)); free(stringValue); }
void MHBitmap::ContentPreparation(MHEngine *engine) { MHVisible::ContentPreparation(engine); if (m_ContentType == IN_NoContent) MHERROR("Bitmap must contain a content"); if (m_ContentType == IN_IncludedContent) // We can't handle included content at the moment. MHERROR("Included content in bitmap is not implemented"); }
// Implement the TestVariable action. Triggers a TestEvent event on the result. void MHIntegerVar::TestVariable(int nOp, const MHUnion &parm, MHEngine *engine) { parm.CheckType(MHUnion::U_Int); bool fRes = false; switch (nOp) { case TC_Equal: fRes = m_nValue == parm.m_nIntVal; break; case TC_NotEqual: fRes = m_nValue != parm.m_nIntVal; break; case TC_Less: fRes = m_nValue < parm.m_nIntVal; break; case TC_LessOrEqual: fRes = m_nValue <= parm.m_nIntVal; break; case TC_Greater: fRes = m_nValue > parm.m_nIntVal; break; case TC_GreaterOrEqual: fRes = m_nValue >= parm.m_nIntVal; break; default: MHERROR("Invalid comparison for int"); // Shouldn't ever happen } MHLOG(MHLogDetail, QString("Comparison %1 between %2 and %3 => %4").arg(TestToText(nOp)) .arg(m_nValue).arg(parm.m_nIntVal).arg(fRes ? "true" : "false")); engine->EventTriggered(this, EventTestEvent, fRes); }
// Implement the TestVariable action. Triggers a TestEvent event on the result. void MHOctetStrVar::TestVariable(int nOp, const MHUnion &parm, MHEngine *engine) { parm.CheckType(MHUnion::U_String); int nRes = m_Value.Compare(parm.m_StrVal); bool fRes = false; switch (nOp) { case TC_Equal: fRes = nRes == 0; break; case TC_NotEqual: fRes = nRes != 0; break; /* case TC_Less: fRes = nRes < 0; break; case TC_LessOrEqual: fRes = nRes <= 0; break; case TC_Greater: fRes = nRes > 0; break; case TC_GreaterOrEqual: fRes = nRes >= 0; break;*/ default: MHERROR("Invalid comparison for string"); // Shouldn't ever happen } MHOctetString sample1(m_Value, 0, 10); MHOctetString sample2(parm.m_StrVal, 0, 10); MHLOG(MHLogDetail, QString("Comparison %1 %2 and %3 => %4").arg(TestToText(nOp)) .arg(sample1.Printable()).arg(sample2.Printable()).arg(fRes ? "true" : "false")); engine->EventTriggered(this, EventTestEvent, fRes); }
// Decode the content. void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *engine) { QRegion updateArea = GetVisibleArea(); // If there's any content already we have to redraw it. if (! m_pContent) return; // Shouldn't happen. int nCHook = m_nContentHook; if (nCHook == 0) nCHook = engine->GetDefaultBitmapCHook(); // TODO: What if we can't convert it? if (nCHook == 4) { // PNG. m_pContent->CreateFromPNG(data, length); } // CHook 5 seems to be used by the BBC on Freesat for an MPEG I-frame for the // background but enabling it here results in it overlaying the video. // Presumably it is not simply the same as CHook 2. else if (nCHook == 2 /* ||nCHook == 5 */) { // MPEG I-frame. m_pContent->CreateFromMPEG(data, length); } else MHERROR(QString("Unknown bitmap content hook %1").arg(nCHook)); updateArea += GetVisibleArea(); // Redraw this bitmap. engine->Redraw(updateArea); // Mark for redrawing // Now signal that the content is available. engine->EventTriggered(this, EventContentAvailable); }
void MHUnion::CheckType(enum UnionTypes t) const { if (m_Type != t) { MHERROR(QString("Type mismatch - expected %1 found %2").arg(GetAsString(m_Type)).arg(GetAsString(t))); } }
// Parse an integer argument. Also used for bool and enum. int MHParseBinary::ParseInt(int endInt) { int intVal = 0; bool firstByte = true; if (endInt == INDEFINITE_LENGTH) { MHERROR("Indefinite length integers are not implemented"); } while (m_p < endInt) { unsigned char ch = GetNextChar(); // Integer values are signed so if the top bit is set in the first byte // we need to set the sign bit. if (firstByte && ch >= 128) { intVal = -1; } firstByte = false; intVal = (intVal << 8) | ch; } return intVal; }
// Return the indirect reference or fail if it's direct MHObjectRef *MHGenericBase::GetReference() { if (m_fIsDirect) { MHERROR("Expected indirect reference"); } return &m_Indirect; }
// Get the next byte. In most all cases it's an error if we reach end-of-file // and we throw an exception. unsigned char MHParseBinary::GetNextChar() { if (m_p >= (int)m_data.size()) { MHERROR("Unexpected end of file"); } return m_data[m_p++]; }
// Implement the TestVariable action. Triggers a TestEvent event on the result. void MHContentRefVar::TestVariable(int nOp, const MHUnion &parm, MHEngine *engine) { parm.CheckType(MHUnion::U_ContentRef); bool fRes = false; switch (nOp) { case TC_Equal: fRes = m_Value.Equal(parm.m_ContentRefVal, engine); break; case TC_NotEqual: fRes = !m_Value.Equal(parm.m_ContentRefVal, engine); break; default: MHERROR("Invalid comparison for content ref"); } engine->EventTriggered(this, EventTestEvent, fRes); }
void MHGenericContentRef::Initialise(MHParseNode *pArg, MHEngine *engine) { if (pArg->GetTagNo() == C_INDIRECTREFERENCE) { // Indirect reference. m_fIsDirect = false; m_Indirect.Initialise(pArg->GetArgN(0), engine); } else if (pArg->GetTagNo() == C_CONTENT_REFERENCE){ // Direct reference. m_fIsDirect = true; m_Direct.Initialise(pArg->GetArgN(0), engine); } else MHERROR("Expected direct or indirect content reference"); }
// Implement the TestVariable action. Triggers a TestEvent event on the result. void MHBooleanVar::TestVariable(int nOp, const MHUnion &parm, MHEngine *engine) { parm.CheckType(MHUnion::U_Bool); bool fRes = false; switch (nOp) { case TC_Equal: fRes = m_fValue == parm.m_fBoolVal; break; case TC_NotEqual: fRes = m_fValue != parm.m_fBoolVal; break; default: MHERROR("Invalid comparison for bool"); } MHLOG(MHLogDetail, QString("Comparison %1 between %2 and %3 => %4").arg(TestToText(nOp)) .arg(m_fValue ? "true" : "false").arg(parm.m_fBoolVal ? "true" : "false").arg(fRes ? "true" : "false")); engine->EventTriggered(this, EventTestEvent, fRes); }
// Content preparation. If it's included we can set up the content. void MHText::ContentPreparation(MHEngine *engine) { MHVisible::ContentPreparation(engine); if (m_ContentType == IN_NoContent) { MHERROR("Text object must have content"); } if (m_ContentType == IN_IncludedContent) { CreateContent(m_IncludedContent.Bytes(), m_IncludedContent.Size(), engine); } }
void MHIngredient::SetData(const MHContentRef &referenced, bool /*fSizeGiven*/, int size, bool fCCGiven, int /*cc*/, MHEngine *engine) { if (m_ContentType != IN_ReferencedContent) { MHERROR("SetData with referenced content applied to an ingredient without referenced content"); } m_ContentRef.Copy(referenced); m_nContentSize = size; if (fCCGiven) { m_nCCPrio = m_nOrigCCPrio; } ContentPreparation(engine); }
void MHEngine::TransitionToScene(const MHObjectRef &target) { int i; if (m_fInTransition) { // TransitionTo is not allowed in OnStartUp or OnCloseDown actions. MHLOG(MHLogWarning, "WARN TransitionTo during transition - ignoring"); return; } if (target.m_GroupId.Size() == 0) { return; // No file name. } QString csPath = GetPathName(target.m_GroupId); // Check that the file exists before we commit to the transition. // This may block if we cannot be sure whether the object is present. QByteArray text; if (! m_Context->GetCarouselData(csPath, text)) { EngineEvent(2); // GroupIDRefError return; } // Parse and run the file. MHGroup *pProgram = ParseProgram(text); if (!pProgram ) MHERROR("Empty scene"); if (pProgram->m_fIsApp) { delete pProgram; MHERROR("Expected a scene"); } // Clear the action queue of anything pending. m_ActionStack.clear(); // At this point we have managed to load the scene. // Deactivate any non-shared ingredients in the application. MHApplication *pApp = CurrentApp(); for (i = pApp->m_Items.Size(); i > 0; i--) { MHIngredient *pItem = pApp->m_Items.GetAt(i - 1); if (! pItem->IsShared()) { pItem->Deactivation(this); // This does not remove them from the display stack. } } m_fInTransition = true; // TransitionTo etc are not allowed. if (pApp->m_pCurrentScene) { pApp->m_pCurrentScene->Deactivation(this); // This may involve a call to RunActions pApp->m_pCurrentScene->Destruction(this); } // Everything that belongs to the previous scene should have been removed from the display stack. // At this point we may have added actions to the queue as a result of synchronous // events during the deactivation. // Remove any events from the asynch event queue unless they derive from // the application itself or a shared ingredient. MHAsynchEvent *pEvent; QQueue<MHAsynchEvent *>::iterator it = m_EventQueue.begin(); while (it != m_EventQueue.end()) { pEvent = *it; if (!pEvent->pEventSource->IsShared()) { delete pEvent; it = m_EventQueue.erase(it); } else { ++it; } } // Can now actually delete the old scene. if (pApp->m_pCurrentScene) { delete(pApp->m_pCurrentScene); pApp->m_pCurrentScene = NULL; } m_Interacting = 0; // Switch to the new scene. CurrentApp()->m_pCurrentScene = static_cast< MHScene* >(pProgram); SetInputRegister(CurrentScene()->m_nEventReg); m_redrawRegion = QRegion(0, 0, CurrentScene()->m_nSceneCoordX, CurrentScene()->m_nSceneCoordY); // Redraw the whole screen if ((__mhlogoptions & MHLogScenes) && __mhlogStream != 0) // Print it so we know what's going on. { pProgram->PrintMe(__mhlogStream, 0); } pProgram->Preparation(this); pProgram->Activation(this); m_fInTransition = false; // The transition is complete }
// Lexical analysis. Get the next symbol. void MHParseText::NextSym() { while (1) { switch (m_ch) { case '\n': m_lineCount++; // And drop to next case ' ': case '\r': case '\t': case '\f': // Skip white space. GetNextChar(); continue; case '/': { // Comment. GetNextChar(); if (m_ch != '/') Error("Malformed comment"); do { GetNextChar(); } while (m_ch != '\n' && m_ch != '\f' && m_ch != '\r'); continue; // Next symbol } case ':': // Start of a tag { m_nType = PTTag; char buff[MAX_TAG_LENGTH+1]; char *p = buff; do { *p++ = m_ch; GetNextChar(); if (p == buff + MAX_TAG_LENGTH) break; } while ((m_ch >= 'a' && m_ch <= 'z') || (m_ch >= 'A' && m_ch <= 'Z')); *p = 0; // Look it up and return it if it's found. m_nTag = FindTag(buff); if (m_nTag >= 0) return; // Unrecognised tag. Error("Unrecognised tag"); } case '"': // Start of a string { m_nType = PTString; // MHEG strings can include NULLs. For the moment we pass back the length and also // null-terminate the strings. m_nStringLength = 0; while (1) { GetNextChar(); if (m_ch == '"') break; // Finished the string. if (m_ch == '\\') GetNextChar(); // Escape character. Include the next char in the string. if (m_ch == '\n' || m_ch == '\r') Error("Unterminated string"); // We grow the buffer to the largest string in the input. unsigned char *str = (unsigned char*)realloc(m_String, m_nStringLength+2); if (str == NULL) Error("Insufficient memory"); m_String = str; m_String[m_nStringLength++] = m_ch; } GetNextChar(); // Skip the closing quote m_String[m_nStringLength] = 0; return; } case '\'': // Start of a string using quoted printable { m_nType = PTString; m_nStringLength = 0; // Quotable printable strings contain escape sequences beginning with the // escape character '='. The strings can span lines but each line must // end with an equal sign. while (1) { GetNextChar(); if (m_ch == '\'') break; if (m_ch == '\n') Error("Unterminated string"); if (m_ch == '=') { // Special code in quoted-printable. // Should be followed by two hex digits or by white space and a newline. GetNextChar(); if (m_ch == ' ' || m_ch == '\t' || m_ch == '\r' || m_ch == '\n') { // White space. Remove everything up to the newline. while (m_ch != '\n') { if (! (m_ch == ' ' || m_ch == '\t' || m_ch == '\r')) { Error("Malformed quoted printable string"); } GetNextChar(); } continue; // continue with the first character on the next line } else { int byte = 0; if (m_ch >= '0' && m_ch <= '9') byte = m_ch - '0'; else if (m_ch >= 'A' && m_ch <= 'F') byte = m_ch - 'A' + 10; else if (m_ch >= 'a' && m_ch <= 'f') byte = m_ch - 'a' + 10; else Error("Malformed quoted printable string"); byte *= 16; GetNextChar(); if (m_ch >= '0' && m_ch <= '9') byte += m_ch - '0'; else if (m_ch >= 'A' && m_ch <= 'F') byte += m_ch - 'A' + 10; else if (m_ch >= 'a' && m_ch <= 'f') byte += m_ch - 'a' + 10; else Error("Malformed quoted printable string"); m_ch = byte; // Put this into the string. } } // We grow the buffer to the largest string in the input. unsigned char *str = (unsigned char*)realloc(m_String, m_nStringLength+2); if (str == NULL) Error("Insufficient memory"); m_String = str; m_String[m_nStringLength++] = m_ch; } GetNextChar(); // Skip the closing quote m_String[m_nStringLength] = 0; return; } case '`': // Start of a string using base 64 // These can, presumably span lines. MHERROR("Base 64 string is not implemented"); break; case '#': // Start of 3-byte hex constant. MHERROR("3-byte hex constant is not implemented"); break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { m_nType = PTInt; bool negative = m_ch == '-'; if (negative) { GetNextChar(); if (m_ch < '0' || m_ch > '9') Error("Expected digit after '-'"); } // Start of a number. Hex can be represented as 0xn. // Strictly speaking hex values cannot be preceded by a minus sign. m_nInt = m_ch - '0'; GetNextChar(); if (m_nInt == 0 && (m_ch == 'x' || m_ch == 'X')) { MHERROR("Hex constant is not implemented"); } while (m_ch >= '0' && m_ch <= '9') { m_nInt = m_nInt * 10 + m_ch - '0'; // TODO: What about overflow? GetNextChar(); } if (negative) m_nInt = -m_nInt; return; } case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': { // Start of an enumerated type. m_nType = PTEnum; char buff[MAX_ENUM+1]; char *p = buff; do { *p++ = m_ch; GetNextChar(); if (p == buff+MAX_ENUM) break; } while ((m_ch >= 'a' && m_ch <= 'z') || (m_ch >= 'A' && m_ch <= 'Z') || m_ch == '-'); *p = '\0'; if (stricmp(buff, "NULL") == 0) { m_nType = PTNull; return; } if (stricmp(buff, "true") == 0) { m_nType = PTBool; m_fBool = true; return; } if (stricmp(buff, "false") == 0) { m_nType = PTBool; m_fBool = false; return; } // Look up the tag in all the tables. Fortunately all the enumerations // are distinct so we don't need to know the context. m_nInt = MHLink::GetEventType(buff); if (m_nInt > 0) return; m_nInt = MHText::GetJustification(buff); if (m_nInt > 0) return; m_nInt = MHText::GetLineOrientation(buff); if (m_nInt > 0) return; m_nInt = MHText::GetStartCorner(buff); if (m_nInt > 0) return; m_nInt = MHSlider::GetOrientation(buff); if (m_nInt > 0) return; m_nInt = MHSlider::GetStyle(buff); if (m_nInt > 0) return; // Check the colour table. If it's there generate a string containing the colour info. for (int i = 0; i < (int)(sizeof(colourTable)/sizeof(colourTable[0])); i++) { if (stricmp(buff, colourTable[i].name) == 0) { m_nType = PTString; unsigned char *str = (unsigned char*)realloc(m_String, 4+1); if (str == NULL) Error("Insufficient memory"); m_String[0] = colourTable[i].r; m_String[1] = colourTable[i].g; m_String[2] = colourTable[i].b; m_String[3] = colourTable[i].t; m_String = str; m_nStringLength = 4; return; } } Error("Unrecognised enumeration"); break; } case '{': // Start of a "section". // The standard indicates that open brace followed by a tag should be written // as a single word. We'll be more lenient and allow spaces or comments between them. m_nType = PTStartSection; GetNextChar(); return; case '}': // End of a "section". m_nType = PTEndSection; GetNextChar(); return; case '(': // Start of a sequence. m_nType = PTStartSeq; GetNextChar(); return; case ')': // End of a sequence. m_nType = PTEndSeq; GetNextChar(); return; case EOF: m_nType = PTEOF; return; default: Error("Unknown character"); GetNextChar(); } } }
void MHParseText::Error(const char *str) { MHERROR(QString("%1- at line %2\n").arg(str).arg(m_lineCount)); }
void MHGroup::Initialise(MHParseNode *p, MHEngine *engine) { engine->GetGroupId().Copy(""); // Set to empty before we start (just in case). MHRoot::Initialise(p, engine); // Must be an external reference with an object number of zero. if (m_ObjectReference.m_nObjectNo != 0 || m_ObjectReference.m_GroupId.Size() == 0) { MHERROR("Object reference for a group object must be zero and external"); } // Set the group id for the rest of the group to this. engine->GetGroupId().Copy(m_ObjectReference.m_GroupId); // Some of the information is irrelevant. // MHParseNode *pStdId = p->GetNamedArg(C_STANDARD_IDENTIFIER); // MHParseNode *pStdVersion = p->GetNamedArg(C_STANDARD_VERSION); // MHParseNode *pObjectInfo = p->GetNamedArg(C_OBJECT_INFORMATION); MHParseNode *pOnStartUp = p->GetNamedArg(C_ON_START_UP); if (pOnStartUp) { m_StartUp.Initialise(pOnStartUp, engine); } MHParseNode *pOnCloseDown = p->GetNamedArg(C_ON_CLOSE_DOWN); if (pOnCloseDown) { m_CloseDown.Initialise(pOnCloseDown, engine); } MHParseNode *pOriginalGCPrio = p->GetNamedArg(C_ORIGINAL_GC_PRIORITY); if (pOriginalGCPrio) { m_nOrigGCPriority = pOriginalGCPrio->GetArgN(0)->GetIntValue(); } // Ignore the other stuff at the moment. MHParseNode *pItems = p->GetNamedArg(C_ITEMS); if (pItems == NULL) { p->Failure("Missing :Items block"); return; } for (int i = 0; i < pItems->GetArgCount(); i++) { MHParseNode *pItem = pItems->GetArgN(i); MHIngredient *pIngredient = NULL; try { // Generate the particular kind of ingredient. switch (pItem->GetTagNo()) { case C_RESIDENT_PROGRAM: pIngredient = new MHResidentProgram; break; case C_REMOTE_PROGRAM: pIngredient = new MHRemoteProgram; break; case C_INTERCHANGED_PROGRAM: pIngredient = new MHInterChgProgram; break; case C_PALETTE: pIngredient = new MHPalette; break; case C_FONT: pIngredient = new MHFont; break; case C_CURSOR_SHAPE: pIngredient = new MHCursorShape; break; case C_BOOLEAN_VARIABLE: pIngredient = new MHBooleanVar; break; case C_INTEGER_VARIABLE: pIngredient = new MHIntegerVar; break; case C_OCTET_STRING_VARIABLE: pIngredient = new MHOctetStrVar; break; case C_OBJECT_REF_VARIABLE: pIngredient = new MHObjectRefVar; break; case C_CONTENT_REF_VARIABLE: pIngredient = new MHContentRefVar; break; case C_LINK: pIngredient = new MHLink; break; case C_STREAM: pIngredient = new MHStream; break; case C_BITMAP: pIngredient = new MHBitmap; break; case C_LINE_ART: pIngredient = new MHLineArt; break; case C_DYNAMIC_LINE_ART: pIngredient = new MHDynamicLineArt; break; case C_RECTANGLE: pIngredient = new MHRectangle; break; case C_HOTSPOT: pIngredient = new MHHotSpot; break; case C_SWITCH_BUTTON: pIngredient = new MHSwitchButton; break; case C_PUSH_BUTTON: pIngredient = new MHPushButton; break; case C_TEXT: pIngredient = new MHText; break; case C_ENTRY_FIELD: pIngredient = new MHEntryField; break; case C_HYPER_TEXT: pIngredient = new MHHyperText; break; case C_SLIDER: pIngredient = new MHSlider; break; case C_TOKEN_GROUP: pIngredient = new MHTokenGroup; break; case C_LIST_GROUP: pIngredient = new MHListGroup; break; default: MHLOG(MHLogWarning, QString("Unknown ingredient %1").arg(pItem->GetTagNo())); // Future proofing: ignore any ingredients that we don't know about. // Obviously these can only arise in the binary coding. } if (pIngredient) { // Initialise it from its argments. pIngredient->Initialise(pItem, engine); // Remember the highest numbered ingredient if (pIngredient->m_ObjectReference.m_nObjectNo > m_nLastId) { m_nLastId = pIngredient->m_ObjectReference.m_nObjectNo; } // Add it to the ingedients of this group. m_Items.Append(pIngredient); } } catch (...) { delete(pIngredient); throw; } } }
virtual void Perform(MHEngine *) { MHERROR(QString("Unimplemented action %1").arg(m_nTag)); }
// Simple recursive parser for ASN1 BER. MHParseNode *MHParseBinary::DoParse() { unsigned char ch; // Tag class enum { Universal, Context/*, Pseudo*/ } tagClass = Universal; // Byte count of end of this item. Set to INDEFINITE_LENGTH if the length is Indefinite. int endOfItem; unsigned int tagNumber = 0; // Read the first character. ch = GetNextChar(); // ASN1 Coding rules: Top two bits (0 and 1) indicate the tag class. // 0x00 - Universal, 0x40 - Application, 0x80 - Context-specific, 0xC0 - Private // We only use Universal and Context. switch (ch & 0xC0) { case 0x00: // Universal tagClass = Universal; break; case 0x80: tagClass = Context; break; default: MHERROR(QString("Invalid tag class = %1").arg(ch, 0, 16)); } // Bit 2 indicates whether it is a simple or compound type. Not used. // Lower bits are the tag number. tagNumber = ch & 0x1f; if (tagNumber == 0x1f) // Except that if it is 0x1F then the tag is encoded in the following bytes. { tagNumber = 0; do { ch = GetNextChar(); tagNumber = (tagNumber << 7) | (ch & 0x7f); } while (ch & 0x80); // Top bit set means there's more to come. } // Next byte is the length. If it is less than 128 it is the actual length, otherwise it // gives the number of bytes containing the length, except that if this is zero the item // has an "indefinite" length and is terminated by two zero bytes. ch = GetNextChar(); if (ch & 0x80) { int lengthOfLength = ch & 0x7f; if (lengthOfLength == 0) { endOfItem = INDEFINITE_LENGTH; } else { endOfItem = 0; while (lengthOfLength--) { ch = GetNextChar(); endOfItem = (endOfItem << 8) | ch; } endOfItem += m_p; } } else { endOfItem = ch + m_p; } if (tagClass == Context) { MHPTagged *pNode = new MHPTagged(tagNumber); try { // The argument here depends on the particular tag we're processing. switch (tagNumber) { case C_MULTIPLE_SELECTION: case C_OBSCURED_INPUT: case C_INITIALLY_AVAILABLE: case C_WRAP_AROUND: case C_TEXT_WRAPPING: case C_INITIALLY_ACTIVE: case C_MOVING_CURSOR: case C_SHARED: case C_ENGINE_RESP: case C_TILING: case C_BORDERED_BOUNDING_BOX: { // BOOL // If there is no argument we need to indicate that so that it gets // the correct default value. if (m_p != endOfItem) { int intVal = ParseInt(endOfItem); // May raise an exception pNode->AddArg(new MHPBool(intVal != 0)); } break; } case C_INPUT_TYPE: case C_SLIDER_STYLE: case C_TERMINATION: case C_ORIENTATION: case C_HORIZONTAL_JUSTIFICATION: case C_BUTTON_STYLE: case C_START_CORNER: case C_LINE_ORIENTATION: case C_VERTICAL_JUSTIFICATION: case C_STORAGE: { // ENUM if (m_p != endOfItem) { int intVal = ParseInt(endOfItem); // May raise an exception pNode->AddArg(new MHPEnum(intVal)); } break; } case C_INITIAL_PORTION: case C_STEP_SIZE: case C_INPUT_EVENT_REGISTER: case C_INITIAL_VALUE: case C_IP_CONTENT_HOOK: case C_MAX_VALUE: case C_MIN_VALUE: case C_LINE_ART_CONTENT_HOOK: case C_BITMAP_CONTENT_HOOK: case C_TEXT_CONTENT_HOOK: case C_STREAM_CONTENT_HOOK: case C_MAX_LENGTH: case C_CHARACTER_SET: case C_ORIGINAL_TRANSPARENCY: case C_ORIGINAL_GC_PRIORITY: case C_LOOPING: case C_ORIGINAL_LINE_STYLE: case C_STANDARD_VERSION: case C_ORIGINAL_LINE_WIDTH: case C_CONTENT_HOOK: case C_CONTENT_CACHE_PRIORITY: case C_COMPONENT_TAG: case C_ORIGINAL_VOLUME: case C_PROGRAM_CONNECTION_TAG: case C_CONTENT_SIZE: { // INT if (m_p != endOfItem) { int intVal = ParseInt(endOfItem); // May raise an exception pNode->AddArg(new MHPInt(intVal)); } break; } case C_OBJECT_INFORMATION: case C_CONTENT_REFERENCE: case C_FONT_ATTRIBUTES: case C_CHAR_LIST: case C_NAME: case C_ORIGINAL_LABEL: { // STRING // Unlike INT, BOOL and ENUM we can't distinguish an empty string // from a missing string. MHOctetString str; ParseString(endOfItem, str); pNode->AddArg(new MHPString(str)); break; } default: { // Everything else has either no argument or is self-describing // TODO: Handle indefinite length. if (endOfItem == INDEFINITE_LENGTH) { MHERROR("Indefinite length arguments are not implemented"); } while (m_p < endOfItem) { pNode->AddArg(DoParse()); } } } } catch (...) { // Memory clean-up delete pNode; throw; } return pNode; } else // Universal - i.e. a primitive type. { // Tag values switch (tagNumber) { case U_BOOL: // Boolean { int intVal = ParseInt(endOfItem); return new MHPBool(intVal != 0); } case U_INT: // Integer { int intVal = ParseInt(endOfItem); return new MHPInt(intVal); } case U_ENUM: // ENUM { int intVal = ParseInt(endOfItem); return new MHPEnum(intVal); } case U_STRING: // String { MHOctetString str; ParseString(endOfItem, str); return new MHPString(str); } case U_NULL: // ASN1 NULL { return new MHPNull; } case U_SEQUENCE: // Sequence { MHParseSequence *pNode = new MHParseSequence(); if (endOfItem == INDEFINITE_LENGTH) { MHERROR("Indefinite length sequences are not implemented"); } try { while (m_p < endOfItem) { pNode->Append(DoParse()); } } catch (...) { // Memory clean-up if error. delete pNode; throw; } return pNode; } default: MHERROR(QString("Unknown universal %1").arg(tagNumber)); } } }
void MHResidentProgram::CallProgram(bool fIsFork, const MHObjectRef &success, const MHSequence<MHParameter *> &args, MHEngine *engine) { if (! m_fAvailable) { Preparation(engine); } // if (m_fRunning) return; // Strictly speaking there should be only one instance of a program running at a time. Activation(engine); MHLOG(MHLogDetail, QString("Calling program %1").arg(m_Name.Printable())); try { // Run the code. if (m_Name.Equal("GCD")) // GetCurrentDate - returns local time. { if (args.Size() == 2) { time_t epochSeconds = 0; short int timeZone = 0; #if HAVE_GETTIMEOFDAY struct timeval time; struct timezone zone; if (gettimeofday(&time, &zone) == -1) { MHLOG(MHLogDetail, QString("gettimeofday() failed")); } epochSeconds = time.tv_sec; timeZone = zone.tz_minuteswest; #elif HAVE_FTIME struct timeb timebuffer; if (ftime(&timebuffer) == -1) { MHLOG(MHLogDetail, QString("ftime() failed")); } epochSeconds = timebuffer.time; timeZone = timebuffer.timezone; #else #error Configuration error? No ftime() or gettimeofday()? #endif // Adjust the time to local. TODO: Check this. epochSeconds -= timeZone * 60; // Time as seconds since midnight. int nTimeAsSecs = epochSeconds % (24 * 60 * 60); // Modified Julian date as number of days since 17th November 1858. // 1st Jan 1970 was date 40587. int nModJulianDate = 40587 + epochSeconds / (24 * 60 * 60); engine->FindObject(*(args.GetAt(0)->GetReference()))->SetVariableValue(nModJulianDate); engine->FindObject(*(args.GetAt(1)->GetReference()))->SetVariableValue(nTimeAsSecs); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("FDa")) // FormatDate { if (args.Size() == 4) { // This is a bit like strftime but not quite. MHOctetString format; GetString(args.GetAt(0), format, engine); int date = GetInt(args.GetAt(1), engine); // As produced in GCD int time = GetInt(args.GetAt(2), engine); // Convert to a Unix date (secs since 1st Jan 1970) but adjusted for time zone. time_t timet = (date - 40587) * (24 * 60 * 60) + time; struct tm *timeStr = gmtime(&timet); MHOctetString result; for (int i = 0; i < format.Size(); i++) { unsigned char ch = format.GetAt(i); char buffer[5]; // Largest text is 4 chars for a year + null terminator if (ch == '%') { i++; if (i == format.Size()) { break; } ch = format.GetAt(i); buffer[0] = 0; switch (ch) { case 'Y': sprintf(buffer, "%04d", timeStr->tm_year + 1900); break; case 'y': sprintf(buffer, "%02d", timeStr->tm_year % 100); break; case 'X': sprintf(buffer, "%02d", timeStr->tm_mon + 1); break; case 'x': sprintf(buffer, "%1d", timeStr->tm_mon + 1); break; case 'D': sprintf(buffer, "%02d", timeStr->tm_mday); break; case 'd': sprintf(buffer, "%1d", timeStr->tm_mday); break; case 'H': sprintf(buffer, "%02d", timeStr->tm_hour); break; case 'h': sprintf(buffer, "%1d", timeStr->tm_hour); break; case 'I': if (timeStr->tm_hour == 12 || timeStr->tm_hour == 0) { strcpy(buffer, "12"); } else { sprintf(buffer, "%02d", timeStr->tm_hour % 12); } break; case 'i': if (timeStr->tm_hour == 12 || timeStr->tm_hour == 0) { strcpy(buffer, "12"); } else { sprintf(buffer, "%1d", timeStr->tm_hour % 12); } break; case 'M': sprintf(buffer, "%02d", timeStr->tm_min); break; case 'm': sprintf(buffer, "%1d", timeStr->tm_min); break; case 'S': sprintf(buffer, "%02d", timeStr->tm_sec); break; case 's': sprintf(buffer, "%1d", timeStr->tm_sec); break; // TODO: These really should be localised. case 'A': if (timeStr->tm_hour < 12) { strcpy(buffer, "AM"); } else { strcpy(buffer, "PM"); } break; case 'a': if (timeStr->tm_hour < 12) { strcpy(buffer, "am"); } else { strcpy(buffer, "pm"); } break; default: buffer[0] = ch; buffer[1] = 0; } result.Append(buffer); } else { result.Append(MHOctetString(&ch, 1)); } } MHParameter *pResString = args.GetAt(3); engine->FindObject(*(pResString->GetReference()))->SetVariableValue(result); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("GDW")) // GetDayOfWeek - returns the day of week that the date occurred on. { if (args.Size() == 2) { int date = GetInt(args.GetAt(0), engine); // Date as produced in GCD // Convert to a Unix date (secs since 1st Jan 1970) but adjusted for time zone. time_t timet = (date - 40587) * (24 * 60 * 60); struct tm *timeStr = gmtime(&timet); // 0 => Sunday, 1 => Monday etc. engine->FindObject(*(args.GetAt(1)->GetReference()))->SetVariableValue(timeStr->tm_wday); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("Rnd")) // Random { if (args.Size() == 2) { int nLimit = GetInt(args.GetAt(0), engine); MHParameter *pResInt = args.GetAt(1); int r = random() % (nLimit + 1); engine->FindObject( *(pResInt->GetReference()))->SetVariableValue(r); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("CTC")) // CastToContentRef { // Converts a string to a ContentRef. if (args.Size() == 2) { MHOctetString string; GetString(args.GetAt(0), string, engine); MHContentRef result; result.m_ContentRef.Copy(string); engine->FindObject(*(args.GetAt(1)->GetReference()))->SetVariableValue(result); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("CTO")) // CastToObjectRef { // Converts a string and an integer to an ObjectRef. if (args.Size() == 3) { MHObjectRef result; GetString(args.GetAt(0), result.m_GroupId, engine); result.m_nObjectNo = GetInt(args.GetAt(1), engine); engine->FindObject(*(args.GetAt(2)->GetReference()))->SetVariableValue(result); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("GSL")) // GetStringLength { if (args.Size() == 2) { // Find a substring within a string and return an index to the position. MHOctetString string; GetString(args.GetAt(0), string, engine); MHParameter *pResInt = args.GetAt(1); SetSuccessFlag(success, true, engine); engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(string.Size()); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("GSS")) // GetSubString { if (args.Size() == 4) // Extract a sub-string from a string. { MHOctetString string; GetString(args.GetAt(0), string, engine); int nBeginExtract = GetInt(args.GetAt(1), engine); int nEndExtract = GetInt(args.GetAt(2), engine); if (nBeginExtract < 1) { nBeginExtract = 1; } if (nBeginExtract > string.Size()) { nBeginExtract = string.Size(); } if (nEndExtract < 1) { nEndExtract = 1; } if (nEndExtract > string.Size()) { nEndExtract = string.Size(); } MHParameter *pResString = args.GetAt(3); // Returns beginExtract to endExtract inclusive. engine->FindObject(*(pResString->GetReference()))->SetVariableValue( MHOctetString(string, nBeginExtract - 1, nEndExtract - nBeginExtract + 1)); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("SSS")) // SearchSubString { if (args.Size() == 4) { // Find a substring within a string and return an index to the position. MHOctetString string, searchString; GetString(args.GetAt(0), string, engine); int nStart = GetInt(args.GetAt(1), engine); if (nStart < 1) { nStart = 1; } GetString(args.GetAt(2), searchString, engine); // Strings are indexed from one. int nPos; for (nPos = nStart - 1; nPos <= string.Size() - searchString.Size(); nPos++) { int i; for (i = 0; i < searchString.Size(); i++) { if (searchString.GetAt(i) != string.GetAt(i + nPos)) { break; } } if (i == searchString.Size()) { break; // Found a match. } } // Set the result. MHParameter *pResInt = args.GetAt(3); SetSuccessFlag(success, true, engine); // Set this first. if (nPos <= string.Size() - searchString.Size()) // Found { // Set the index to the position of the string, counting from 1. engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(nPos + 1); } else // Not found. Set the result index to -1 { engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(-1); } } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("SES")) // SearchAndExtractSubString { if (args.Size() == 5) { // Find a substring within a string and return an index to the position // and the prefix to the substring. MHOctetString string, searchString; GetString(args.GetAt(0), string, engine); int nStart = GetInt(args.GetAt(1), engine); if (nStart < 1) { nStart = 1; } GetString(args.GetAt(2), searchString, engine); // Strings are indexed from one. int nPos; for (nPos = nStart - 1; nPos <= string.Size() - searchString.Size(); nPos++) { int i; for (i = 0; i < searchString.Size(); i++) { if (searchString.GetAt(i) != string.GetAt(i + nPos)) { break; // Doesn't match } } if (i == searchString.Size()) { break; // Found a match. } } // Set the results. MHParameter *pResString = args.GetAt(3); MHParameter *pResInt = args.GetAt(4); SetSuccessFlag(success, true, engine); // Set this first. if (nPos <= string.Size() - searchString.Size()) // Found { // Set the index to the position AFTER the string, counting from 1. engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(nPos + 1 + searchString.Size()); // Return the sequence from nStart-1 of length nPos-nStart+1 MHOctetString resultString(string, nStart - 1, nPos - nStart + 1); engine->FindObject(*(pResString->GetReference()))->SetVariableValue(resultString); } else // Not found. Set the result string to empty and the result index to -1 { engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(-1); engine->FindObject(*(pResString->GetReference()))->SetVariableValue(MHOctetString("")); } } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("GSI")) // SI_GetServiceIndex { // Returns an index indicating the service if (args.Size() == 2) { MHOctetString string; GetString(args.GetAt(0), string, engine); MHParameter *pResInt = args.GetAt(1); // The format of the service is dvb://netID.[transPortID].serviceID // where the IDs are in hex. // or rec://svc/lcn/N where N is the "logical channel number" i.e. the Freeview channel. QString str = QString::fromUtf8((const char *)string.Bytes(), string.Size()); int nResult = engine->GetContext()->GetChannelIndex(str); engine->FindObject(*(pResInt->GetReference()))->SetVariableValue(nResult); SetSuccessFlag(success, nResult >= 0, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("TIn")) // SI_TuneIndex - Fork not allowed { // Tunes to an index returned by GSI if (args.Size() == 1) { int nChannel = GetInt(args.GetAt(0), engine); bool res = nChannel >= 0 ? engine->GetContext()->TuneTo( nChannel, engine->GetTuneInfo()) : false; SetSuccessFlag(success, res, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("TII")) // SI_TuneIndexInfo { // Indicates whether to perform a subsequent TIn quietly or normally. if (args.Size() == 1) { int tuneinfo = GetInt(args.GetAt(0), engine); engine->SetTuneInfo(tuneinfo); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("BSI")) // SI_GetBasicSI { // Returns basic SI information about the service indicated by an index // returned by GSI. // Returns networkID, origNetworkID, transportStreamID, serviceID if (args.Size() == 5) { int channelId = GetInt(args.GetAt(0), engine); int netId, origNetId, transportId, serviceId; // Look the information up in the database. bool res = engine->GetContext()->GetServiceInfo(channelId, netId, origNetId, transportId, serviceId); if (res) { engine->FindObject(*(args.GetAt(1)->GetReference()))->SetVariableValue(netId); engine->FindObject(*(args.GetAt(2)->GetReference()))->SetVariableValue(origNetId); engine->FindObject(*(args.GetAt(3)->GetReference()))->SetVariableValue(transportId); engine->FindObject(*(args.GetAt(4)->GetReference()))->SetVariableValue(serviceId); } SetSuccessFlag(success, res, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("GBI")) // GetBootInfo { // Gets the NB_info field. MHERROR("GetBootInfo ResidentProgram is not implemented"); } else if (m_Name.Equal("CCR")) // CheckContentRef { // Sees if an item with a particular content reference is available // in the carousel. This looks like it should block until the file // is available. The profile recommends that this should be forked // rather than called. if (args.Size() == 3) { MHUnion un; un.GetValueFrom(*(args.GetAt(0)), engine); un.CheckType(MHUnion::U_ContentRef); MHContentRef fileName; fileName.Copy(un.m_ContentRefVal); QString csPath = engine->GetPathName(fileName.m_ContentRef); bool result = false; QByteArray text; // Try to load the object. if (! csPath.isEmpty()) { result = engine->GetContext()->GetCarouselData(csPath, text); } // Set the result variable. MHParameter *pResFlag = args.GetAt(1); engine->FindObject(*(pResFlag->GetReference()))->SetVariableValue(result); MHParameter *pResCR = args.GetAt(2); // Copy the file name to the resulting content ref. engine->FindObject(*(pResCR->GetReference()))->SetVariableValue(fileName); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("CGR")) // CheckGroupIDRef { // Sees if an application or scene with a particular group id // is available in the carousel. MHERROR("CheckGroupIDRef ResidentProgram is not implemented"); } else if (m_Name.Equal("VTG")) // VideoToGraphics { // Video to graphics transformation. MHERROR("VideoToGraphics ResidentProgram is not implemented"); } else if (m_Name.Equal("SWA")) // SetWidescreenAlignment { // Sets either LetterBox or Centre-cut-out mode. // Seems to be concerned with aligning a 4:3 scene with an underlying 16:9 video MHERROR("SetWidescreenAlignment ResidentProgram is not implemented"); } else if (m_Name.Equal("GDA")) // GetDisplayAspectRatio { // Returns the aspcet ratio. 4:3 => 1, 16:9 => 2 MHERROR("GetDisplayAspectRatio ResidentProgram is not implemented"); } else if (m_Name.Equal("CIS")) // CI_SendMessage { // Sends a message to a DVB CI application MHERROR("CI_SendMessage ResidentProgram is not implemented"); } else if (m_Name.Equal("SSM")) // SetSubtitleMode { // Enable or disable subtitles in addition to MHEG. if (args.Size() == 1) { bool status = GetBool(args.GetAt(0), engine); MHLOG(MHLogNotifications, QString("NOTE SetSubtitleMode %1") .arg(status ? "enabled" : "disabled")); // TODO Notify player SetSuccessFlag(success, true, engine); } else SetSuccessFlag(success, false, engine); } else if (m_Name.Equal("WAI")) // WhoAmI { // Return a concatenation of the strings we respond to in // GetEngineSupport(UKEngineProfile(X)) if (args.Size() == 1) { MHOctetString result; result.Copy(engine->MHEGEngineProviderIdString); result.Append(" "); result.Append(engine->GetContext()->GetReceiverId()); result.Append(" "); result.Append(engine->GetContext()->GetDSMCCId()); engine->FindObject(*(args.GetAt(0)->GetReference()))->SetVariableValue(result); SetSuccessFlag(success, true, engine); } else { SetSuccessFlag(success, false, engine); } } else if (m_Name.Equal("DBG")) // Debug - optional { QString message = "DEBUG: "; for (int i = 0; i < args.Size(); i++) { MHUnion un; un.GetValueFrom(*(args.GetAt(i)), engine); switch (un.m_Type) { case MHUnion::U_Int: message.append(QString("%1").arg(un.m_nIntVal)); break; case MHParameter::P_Bool: message.append(un.m_fBoolVal ? "True" : "False"); break; case MHParameter::P_String: message.append(QString::fromUtf8((const char *)un.m_StrVal.Bytes(), un.m_StrVal.Size())); break; case MHParameter::P_ObjRef: message.append(un.m_ObjRefVal.Printable()); break; case MHParameter::P_ContentRef: message.append(un.m_ContentRefVal.Printable()); break; case MHParameter::P_Null: break; } } MHLOG(MHLogNotifications, message); } else if (m_Name.Equal("SBI")) // SetBroadcastInterrupt { // Required for NativeApplicationExtension // En/dis/able program interruptions e.g. green button if (args.Size() == 1) { bool status = GetBool(args.GetAt(0), engine); MHLOG(MHLogNotifications, QString("NOTE SetBroadcastInterrupt %1") .arg(status ? "enabled" : "disabled")); // Nothing todo at present SetSuccessFlag(success, true, engine); } else SetSuccessFlag(success, false, engine); } // InteractionChannelExtension else if (m_Name.Equal("GIS")) { // GetICStatus if (args.Size() == 1) { int ICstatus = engine->GetContext()->GetICStatus(); MHLOG(MHLogNotifications, "NOTE InteractionChannel " + QString( ICstatus == 0 ? "active" : ICstatus == 1 ? "inactive" : ICstatus == 2 ? "disabled" : "undefined")); engine->FindObject(*(args.GetAt(0)->GetReference()))->SetVariableValue(ICstatus); SetSuccessFlag(success, true, engine); } else SetSuccessFlag(success, false, engine); } else if (m_Name.Equal("RDa")) { // ReturnData if (args.Size() >= 3) { MHOctetString string; GetString(args.GetAt(0), string, engine); QString url = QString::fromUtf8((const char *)string.Bytes(), string.Size()); // Variable name/value pairs QStringList params; int i = 1; for (; i + 2 < args.Size(); i += 2) { GetString(args.GetAt(i), string, engine); QString name = QString::fromUtf8((const char *)string.Bytes(), string.Size()); QString val; MHUnion un; un.GetValueFrom(*(args.GetAt(i+1)), engine); switch (un.m_Type) { case MHUnion::U_Int: val = QString::number(un.m_nIntVal); break; case MHParameter::P_Bool: val = un.m_fBoolVal ? "true" : "false"; break; case MHParameter::P_String: val = QString::fromUtf8((const char*)un.m_StrVal.Bytes(), un.m_StrVal.Size()); break; case MHParameter::P_ObjRef: val = un.m_ObjRefVal.Printable(); break; case MHParameter::P_ContentRef: val = un.m_ContentRefVal.Printable(); break; case MHParameter::P_Null: val = "<NULL>"; break; default: val = QString("<type %1>").arg(un.m_Type); break; } params += name + "=" + val; } // TODO MHLOG(MHLogNotifications, "NOTE ReturnData '" + url + "' { " + params.join(" ") + " }"); // HTTP response code, 0= none engine->FindObject(*(args.GetAt(i)->GetReference()))->SetVariableValue(0); // HTTP response data string = ""; engine->FindObject(*(args.GetAt(i+1)->GetReference()))->SetVariableValue(string); SetSuccessFlag(success, false, engine); } else SetSuccessFlag(success, false, engine); } else if (m_Name.Equal("SHF")) { // SetHybridFileSystem if (args.Size() == 2) { MHOctetString string; GetString(args.GetAt(0), string, engine); QString str = QString::fromUtf8((const char *)string.Bytes(), string.Size()); GetString(args.GetAt(1), string, engine); QString str2 = QString::fromUtf8((const char *)string.Bytes(), string.Size()); // TODO MHLOG(MHLogNotifications, QString("NOTE SetHybridFileSystem %1=%2") .arg(str).arg(str2)); SetSuccessFlag(success, false, engine); } else SetSuccessFlag(success, false, engine); } else { MHERROR(QString("Unknown ResidentProgram %1").arg(m_Name.Printable())); } } catch (char const *) { // If something went wrong set the succeeded flag to false SetSuccessFlag(success, false, engine); // And continue on. In particular we need to deactivate. } Deactivation(engine); // At the moment we always treat Fork as Call. If we do get a Fork we should signal that we're done. if (fIsFork) { engine->EventTriggered(this, EventAsyncStopped); } }
// Report a failure. This can be called when we use the parse tree to set up object tree. void MHParseNode::Failure(const char *p) { MHERROR(p); }