// See if we can satisfy any of the outstanding requests. void MHEngine::CheckContentRequests() { QList<MHExternContent*>::iterator it = m_ExternContentTable.begin(); while (it != m_ExternContentTable.end()) { MHExternContent *pContent = *it; if (m_Context->CheckCarouselObject(pContent->m_FileName)) { // Remove from the list. it = m_ExternContentTable.erase(it); QByteArray text; if (m_Context->GetCarouselData(pContent->m_FileName, text)) { MHLOG(MHLogNotifications, QString("Received %1 len %2") .arg(pContent->m_pRequester->m_ObjectReference.Printable()) .arg(text.size()) ); // If the content is not recognized catch the exception and continue try { pContent->m_pRequester->ContentArrived( reinterpret_cast< const unsigned char * >(text.constData()), text.size(), this); } catch (char const *) {} } else { MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2") .arg(pContent->m_pRequester->m_ObjectReference.Printable()) .arg(pContent->m_FileName)); if (kProtoHTTP == PathProtocol(pContent->m_FileName)) EngineEvent(203); // 203=RemoteNetworkError if 404 reply EngineEvent(3); // ContentRefError } delete pContent; } else if (pContent->m_time.elapsed() > 60000) // TODO Get this from carousel { // Remove from the list. it = m_ExternContentTable.erase(it); MHLOG(MHLogWarning, QString("WARN File timed out %1 <= %2") .arg(pContent->m_pRequester->m_ObjectReference.Printable()) .arg(pContent->m_FileName)); if (kProtoHTTP == PathProtocol(pContent->m_FileName)) EngineEvent(203); // 203=RemoteNetworkError if 404 reply EngineEvent(3); // ContentRefError delete pContent; } else { ++it; } } }
// Called by an ingredient wanting external content. void MHEngine::RequestExternalContent(MHIngredient *pRequester) { // It seems that some MHEG applications contain active ingredients with empty contents // This isn't correct but we simply ignore that. if (! pRequester->m_ContentRef.IsSet()) { return; } // Remove any existing content requests for this ingredient. CancelExternalContentRequest(pRequester); QString csPath = GetPathName(pRequester->m_ContentRef.m_ContentRef); if (csPath.isEmpty()) { MHLOG(MHLogWarning, "RequestExternalContent empty path"); return; } if (m_Context->CheckCarouselObject(csPath)) { // Available now - pass it to the ingredient. QByteArray text; if (m_Context->GetCarouselData(csPath, text)) { // If the content is not recognized catch the exception and continue try { pRequester->ContentArrived( reinterpret_cast< const unsigned char * >(text.constData()), text.size(), this); } catch (char const *) {} } else { MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2") .arg(pRequester->m_ObjectReference.Printable()).arg(csPath)); if (kProtoHTTP == PathProtocol(csPath)) EngineEvent(203); // 203=RemoteNetworkError if 404 reply EngineEvent(3); // ContentRefError } } else { // Need to record this and check later. MHLOG(MHLogNotifications, QString("Waiting for %1 <= %2") .arg(pRequester->m_ObjectReference.Printable()).arg(csPath.left(128)) ); MHExternContent *pContent = new MHExternContent; pContent->m_FileName = csPath; pContent->m_pRequester = pRequester; pContent->m_time.start(); m_ExternContentTable.append(pContent); } }
// 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); }
// Return the colour, looking up in the palette if necessary. Used by the sub-classes MHRgba MHVisible::GetColour(const MHColour &colour) { int red = 0, green = 0, blue = 0, alpha = 0; int cSize = colour.m_ColStr.Size(); if (cSize != 4) { MHLOG(MHLogWarning, QString("Colour string has length %1 not 4.").arg(cSize)); } // Just in case the length is short we handle those properly. if (cSize > 0) { red = colour.m_ColStr.GetAt(0); } if (cSize > 1) { green = colour.m_ColStr.GetAt(1); } if (cSize > 2) { blue = colour.m_ColStr.GetAt(2); } if (cSize > 3) { alpha = 255 - colour.m_ColStr.GetAt(3); // Convert transparency to alpha } return MHRgba(red, green, blue, alpha); }
void MHEngine::EngineEvent(int nCode) { if (CurrentApp()) EventTriggered(CurrentApp(), EventEngineEvent, nCode); else if (!m_fBooting) MHLOG(MHLogWarning, QString("WARN EngineEvent %1 but no app").arg(nCode)); }
// 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); }
// Called when an event is triggered. Either queues the event or finds a link that matches. void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion &evData) { MHLOG(MHLogLinks, QString("Event - %1 from %2") .arg(MHLink::EventTypeToString(ev)).arg(pSource->m_ObjectReference.Printable())); switch (ev) { case EventFirstItemPresented: case EventHeadItems: case EventHighlightOff: case EventHighlightOn: case EventIsAvailable: case EventIsDeleted: case EventIsDeselected: case EventIsRunning: case EventIsSelected: case EventIsStopped: case EventItemDeselected: case EventItemSelected: case EventLastItemPresented: case EventTailItems: case EventTestEvent: case EventTokenMovedFrom: case EventTokenMovedTo: // Synchronous events. Fire any links that are waiting. // The UK MHEG describes this as the preferred interpretation. We are checking the link // at the time we generate the event rather than queuing the synchronous events until // this elementary action is complete. That matters if we are processing an elementary action // which will activate or deactivate links. CheckLinks(pSource->m_ObjectReference, ev, evData); break; case EventAnchorFired: case EventAsyncStopped: case EventContentAvailable: case EventCounterTrigger: case EventCursorEnter: case EventCursorLeave: case EventEngineEvent: case EventEntryFieldFull: case EventInteractionCompleted: case EventStreamEvent: case EventStreamPlaying: case EventStreamStopped: case EventTimerFired: case EventUserInput: case EventFocusMoved: // UK MHEG. Generated by HyperText class case EventSliderValueChanged: // UK MHEG. Generated by Slider class default: { // Asynchronous events. Add to the event queue. MHAsynchEvent *pEvent = new MHAsynchEvent; pEvent->pEventSource = pSource; pEvent->eventType = ev; pEvent->eventData = evData; m_EventQueue.enqueue(pEvent); } break; } }
void MHStream::Initialise(MHParseNode *p, MHEngine *engine) { MHPresentable::Initialise(p, engine); MHParseNode *pMultiplex = p->GetNamedArg(C_MULTIPLEX); if (pMultiplex) { for (int i = 0; i < pMultiplex->GetArgCount(); i++) { MHParseNode *pItem = pMultiplex->GetArgN(i); if (pItem->GetTagNo() == C_AUDIO) { MHAudio *pAudio = new MHAudio; m_Multiplex.Append(pAudio); pAudio->Initialise(pItem, engine); } else if (pItem->GetTagNo() == C_VIDEO) { MHVideo *pVideo = new MHVideo; m_Multiplex.Append(pVideo); pVideo->Initialise(pItem, engine); } else if (pItem->GetTagNo() == C_RTGRAPHICS) { MHRTGraphics *pRtGraph = new MHRTGraphics; m_Multiplex.Append(pRtGraph); pRtGraph->Initialise(pItem, engine); } else { // Ignore unknown items MHLOG(MHLogWarning, QString("WARN unknown stream type %1") .arg(pItem->GetTagNo())); } } } MHParseNode *pStorage = p->GetNamedArg(C_STORAGE); if (pStorage) { m_nStorage = (enum Storage) pStorage->GetArgN(0)->GetEnumValue(); } MHParseNode *pLooping = p->GetNamedArg(C_LOOPING); if (pLooping) { m_nLooping = pLooping->GetArgN(0)->GetIntValue(); } }
// 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); }
// Implement the SetVariable action. void MHOctetStrVar::SetVariableValue(const MHUnion &value) { if (value.m_Type == MHUnion::U_Int) { // Implicit conversion of int to string. char buff[30]; // 30 chars is more than enough. snprintf(buff, sizeof(buff), "%0d", value.m_nIntVal); m_Value.Copy(buff); } else { value.CheckType(MHUnion::U_String); m_Value.Copy(value.m_StrVal); } // Debug MHOctetString sample(m_Value, 0, 10); MHLOG(MHLogDetail, QString("Update %1 := %2").arg(m_ObjectReference.Printable()) .arg(sample.Printable())); }
// Implement the SetVariable action. Also used as part of Add, Subtract etc void MHIntegerVar::SetVariableValue(const MHUnion &value) { if (value.m_Type == MHUnion::U_String) { // Implicit conversion of string to integer. int v = 0; int p = 0; bool fNegative = false; if (value.m_StrVal.Size() > 0 && value.m_StrVal.GetAt(0) == '-') { p++; fNegative = true; } for (; p < value.m_StrVal.Size(); p++) { unsigned char ch = value.m_StrVal.GetAt(p); if (ch < '0' || ch > '9') { break; } v = v * 10 + ch - '0'; } if (fNegative) { m_nValue = -v; } else { m_nValue = v; } } else { value.CheckType(MHUnion::U_Int); m_nValue = value.m_nIntVal; } MHLOG(MHLogDetail, QString("Update %1 := %2").arg(m_ObjectReference.Printable()).arg(m_nValue)); }
void MHEngine::Quit() { if (m_fInTransition) { MHLOG(MHLogWarning, "WARN Quit during transition - ignoring"); return; } m_fInTransition = true; // Starting a transition if (CurrentScene()) { CurrentScene()->Destruction(this); } CurrentApp()->Destruction(this); // This isn't in the standard as far as I can tell but we have to do this because // we may have events referring to the old application. while (!m_EventQueue.isEmpty()) { delete m_EventQueue.dequeue(); } delete m_ApplicationStack.pop(); // If the stack is now empty we return to boot mode. if (m_ApplicationStack.isEmpty()) { m_fBooting = true; } else { CurrentApp()->m_fRestarting = true; CurrentApp()->Activation(this); // This will do any OnRestart actions. // Note - this doesn't activate the previously active scene. } m_fInTransition = false; // The transition is complete }
// Provide updated content. void MHIngredient::SetData(const MHOctetString &included, MHEngine *engine) { // If the content is currently Included then the data should be Included // and similarly for Referenced content. I've seen cases where SetData // with included content has been used erroneously with the intention that // this should be the file name for referenced content. if (m_ContentType == IN_ReferencedContent) { m_ContentRef.m_ContentRef.Copy(included); } else if (m_ContentType == IN_IncludedContent) { m_IncludedContent.Copy(included); } else { MHLOG(MHLogWarning, "SetData with no content"); // MHEG Error } ContentPreparation(engine); }
// Remove any pending requests from the queue. void MHEngine::CancelExternalContentRequest(MHIngredient *pRequester) { QList<MHExternContent *>::iterator it = m_ExternContentTable.begin(); MHExternContent *pContent; while (it != m_ExternContentTable.end()) { pContent = *it; if (pContent->m_pRequester == pRequester) { MHLOG(MHLogNotifications, QString("Cancelled wait for %1") .arg(pRequester->m_ObjectReference.Printable()) ); it = m_ExternContentTable.erase(it); delete pContent; return; } else { ++it; } } }
// Look up an object. In most cases we just want to fail if the object isn't found. MHRoot *MHEngine::FindObject(const MHObjectRef &oRef, bool failOnNotFound) { // It should match either the application or the scene. MHGroup *pSearch = NULL; MHGroup *pScene = CurrentScene(), *pApp = CurrentApp(); if (pScene && GetPathName(pScene->m_ObjectReference.m_GroupId) == GetPathName(oRef.m_GroupId)) { pSearch = pScene; } else if (pApp && GetPathName(pApp->m_ObjectReference.m_GroupId) == GetPathName(oRef.m_GroupId)) { pSearch = pApp; } if (pSearch) { MHRoot *pItem = pSearch->FindByObjectNo(oRef.m_nObjectNo); if (pItem) { return pItem; } } if (failOnNotFound) { // I've seen at least one case where MHEG code has quite deliberately referred to // an object that may or may not exist at a particular time. // Another case was a call to CallActionSlot with an object reference variable // that had been initialised to zero. MHLOG(MHLogWarning, QString("WARN Reference %1 not found").arg(oRef.m_nObjectNo)); throw "FindObject failed"; } return NULL; // If we don't generate an error. }
// This is the main loop of the engine. int MHEngine::RunAll() { // Request to boot or reboot if (m_fBooting) { // Reset everything while (!m_ApplicationStack.isEmpty()) { delete m_ApplicationStack.pop(); } while (!m_EventQueue.isEmpty()) { delete m_EventQueue.dequeue(); } while (!m_ExternContentTable.isEmpty()) { delete m_ExternContentTable.takeFirst(); } m_LinkTable.clear(); // UK MHEG applications boot from ~//a or ~//startup. Actually the initial // object can also be explicitly given in the MHObjectRef startObj; startObj.m_nObjectNo = 0; startObj.m_GroupId.Copy(MHOctetString("~//a")); // Launch will block until either it finds the appropriate object and // begins the application or discovers that the file definitely isn't // present in the carousel. It is possible that the object might appear // if one of the containing directories is updated. if (! Launch(startObj)) { startObj.m_GroupId.Copy(MHOctetString("~//startup")); if (! Launch(startObj)) { MHLOG(MHLogNotifications, "NOTE Engine auto-boot failed"); return -1; } } m_fBooting = false; } int nNextTime = 0; do { // Check to see if we need to close. if (m_Context->CheckStop()) { return 0; } // Run queued actions. RunActions(); // Now the action stack is empty process the next asynchronous event. // Processing one event may affect how subsequent events are handled. // Check to see if some files we're waiting for have arrived. // This could result in ContentAvailable events. CheckContentRequests(); // Check the timers. This may result in timer events being raised. nNextTime = CurrentScene() ? CurrentScene()->CheckTimers(this) : 0; if (CurrentApp()) { // The UK MHEG profile allows applications to have timers. int nAppTime = CurrentApp()->CheckTimers(this); if (nAppTime != 0 && (nNextTime == 0 || nAppTime < nNextTime)) { nNextTime = nAppTime; } } if (! m_ExternContentTable.isEmpty()) { // If we have an outstanding request for external content we need to set a timer. if (nNextTime == 0 || nNextTime > CONTENT_CHECK_TIME) { nNextTime = CONTENT_CHECK_TIME; } } if (! m_EventQueue.isEmpty()) { MHAsynchEvent *pEvent = m_EventQueue.dequeue(); MHLOG(MHLogLinks, QString("Asynchronous event dequeued - %1 from %2") .arg(MHLink::EventTypeToString(pEvent->eventType)) .arg(pEvent->pEventSource->m_ObjectReference.Printable())); CheckLinks(pEvent->pEventSource->m_ObjectReference, pEvent->eventType, pEvent->eventData); delete pEvent; } } while (! m_EventQueue.isEmpty() || ! m_ActionStack.isEmpty()); // Redraw the display if necessary. if (! m_redrawRegion.isEmpty()) { m_Context->RequireRedraw(m_redrawRegion); m_redrawRegion = QRegion(); } return nNextTime; }
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 }
// Launch and Spawn bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn) { if (m_fInTransition) { MHLOG(MHLogWarning, "WARN Launch during transition - ignoring"); return false; } if (target.m_GroupId.Size() == 0) return false; // No file name. QString csPath = GetPathName(target.m_GroupId); // Get path relative to root. // 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)) { if (!m_fBooting) EngineEvent(2); // GroupIDRefError return false; } MHApplication *pProgram = (MHApplication*)ParseProgram(text); if (! pProgram) { MHLOG(MHLogWarning, "Empty application"); return false; } if (! pProgram->m_fIsApp) { MHLOG(MHLogWarning, "Expected an application"); delete pProgram; return false; } if ((__mhlogoptions & MHLogScenes) && __mhlogStream != 0) // Print it so we know what's going on. { pProgram->PrintMe(__mhlogStream, 0); } // Clear the action queue of anything pending. m_ActionStack.clear(); m_fInTransition = true; // Starting a transition try { if (CurrentApp()) { if (fIsSpawn) // Run the CloseDown actions. { AddActions(CurrentApp()->m_CloseDown); RunActions(); } if (CurrentScene()) { CurrentScene()->Destruction(this); } CurrentApp()->Destruction(this); if (!fIsSpawn) { delete m_ApplicationStack.pop(); // Pop and delete the current app. } } // Save the path we use for this app. pProgram->m_Path = csPath; // Record the path int nPos = pProgram->m_Path.lastIndexOf('/'); if (nPos < 0) { pProgram->m_Path = ""; } else { pProgram->m_Path = pProgram->m_Path.left(nPos); } // Have now got the application. m_ApplicationStack.push(pProgram); // This isn't in the standard as far as I can tell but we have to do this because // we may have events referring to the old application. while (!m_EventQueue.isEmpty()) { delete m_EventQueue.dequeue(); } // Activate the application. .... CurrentApp()->Activation(this); m_fInTransition = false; // The transition is complete return true; } catch (...) { m_fInTransition = false; // The transition is complete return false; } }
// Implement the SetVariable action. void MHBooleanVar::SetVariableValue(const MHUnion &value) { value.CheckType(MHUnion::U_Bool); m_fValue = value.m_fBoolVal; MHLOG(MHLogDetail, QString("Update %1 := %2").arg(m_ObjectReference.Printable()).arg(m_fValue ? "true" : "false")); }
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); } }
// Recreate the image. void MHText::Redraw() { if (! m_fRunning || !m_pDisplay) { return; } if (m_nBoxWidth == 0 || m_nBoxHeight == 0) { return; // Can't draw zero sized boxes. } m_pDisplay->SetSize(m_nBoxWidth, m_nBoxHeight); m_pDisplay->Clear(); MHRgba textColour = GetColour(m_textColour); // Process any escapes in the text and construct the text arrays. MHSequence <MHTextLine *> theText; // Set up the first item on the first line. MHTextItem *pCurrItem = new MHTextItem; MHTextLine *pCurrLine = new MHTextLine; pCurrLine->m_Items.Append(pCurrItem); theText.Append(pCurrLine); MHStack <MHRgba> m_ColourStack; // Stack to handle nested colour codes. m_ColourStack.Push(textColour); pCurrItem->m_Colour = textColour; // FILE *fd=stdout; fprintf(fd, "Redraw Text "); m_Content.PrintMe(fd, 0); fprintf(fd, "\n"); int i = 0; while (i < m_Content.Size()) { unsigned char ch = m_Content.GetAt(i++); if (ch == 0x09) // Tab - start a new item if we have any text in the existing one. { if (pCurrItem->m_Text.Size() != 0) { pCurrItem = pCurrItem->NewItem(); pCurrLine->m_Items.Append(pCurrItem); } if (m_HorizJ == Start) pCurrItem->m_nTabCount++; } else if (ch == 0x0d) // CR - line break. { // TODO: Two CRs next to one another are treated as </P> rather than <BR><BR> // This should also include the sequence CRLFCRLF. pCurrLine = new MHTextLine; theText.Append(pCurrLine); pCurrItem = pCurrItem->NewItem(); pCurrLine->m_Items.Append(pCurrItem); } else if (ch == 0x1b) // Escape - special codes. { if (i == m_Content.Size()) { break; } unsigned char code = m_Content.GetAt(i); // The only codes we are interested in are the start and end of colour. // TODO: We may also need "bold" and some hypertext colours. if (code >= 0x40 && code <= 0x5e) // Start code { // Start codes are followed by a parameter count and a number of parameter bytes. if (++i == m_Content.Size()) { break; } unsigned char paramCount = m_Content.GetAt(i); i++; if (code == 0x43 && paramCount == 4 && i + paramCount <= m_Content.Size()) { // Start of colour. if (pCurrItem->m_Text.Size() != 0) { pCurrItem = pCurrItem->NewItem(); pCurrLine->m_Items.Append(pCurrItem); } pCurrItem->m_Colour = MHRgba(m_Content.GetAt(i), m_Content.GetAt(i + 1), m_Content.GetAt(i + 2), 255 - m_Content.GetAt(i + 3)); // Push this colour onto the colour stack. m_ColourStack.Push(pCurrItem->m_Colour); } else { MHLOG(MHLogWarning, QString("Unknown text escape code 0x%1").arg(code, 2, 16)); } i += paramCount; // Skip the parameters } else if (code >= 0x60 && code <= 0x7e) // End code. { i++; if (code == 0x63) { if (m_ColourStack.Size() > 1) { m_ColourStack.Pop(); // Start a new item since we're using a new colour. if (pCurrItem->m_Text.Size() != 0) { pCurrItem = pCurrItem->NewItem(); pCurrLine->m_Items.Append(pCurrItem); } // Set the subsequent text in the colour we're using now. pCurrItem->m_Colour = m_ColourStack.Top(); } } else MHLOG(MHLogWarning, QString("Unknown text escape code 0x%1").arg(code,2,16)); } else MHLOG(MHLogWarning, QString("Unknown text escape code 0x%1").arg(code,2,16)); } else if (ch <= 0x1f) { // Certain characters including LF and the marker codes between 0x1c and 0x1f are // explicitly intended to be ignored. Include all the other codes. } else // Add to the current text. { int nStart = i - 1; while (i < m_Content.Size() && m_Content.GetAt(i) >= 0x20) { i++; } pCurrItem->m_Text.Append(MHOctetString(m_Content, nStart, i - nStart)); } } // Set up the initial attributes. int style, size, lineSpace, letterSpace; InterpretAttributes(m_fontAttrs, style, size, lineSpace, letterSpace); // Create a font with this information. m_pDisplay->SetFont(size, (style & 2) != 0, (style & 1) != 0); // Calculate the layout of each section. for (i = 0; i < theText.Size(); i++) { MHTextLine *pLine = theText.GetAt(i); pLine->m_nLineWidth = 0; for (int j = 0; j < pLine->m_Items.Size(); j++) { MHTextItem *pItem = pLine->m_Items.GetAt(j); // Set any tabs. pLine->m_nLineWidth = Tabs(pLine->m_nLineWidth, pItem->m_nTabCount); if (pItem->m_Unicode.isEmpty()) // Convert UTF-8 to Unicode. { int s = pItem->m_Text.Size(); pItem->m_Unicode = QString::fromUtf8((const char *)pItem->m_Text.Bytes(), s); pItem->m_nUnicode = pItem->m_Unicode.length(); } // Fit the text onto the line. int nFullText = pItem->m_nUnicode; // Get the box size and update pItem->m_nUnicode to the number that will fit. QRect rect = m_pDisplay->GetBounds(pItem->m_Unicode, pItem->m_nUnicode, m_nBoxWidth - pLine->m_nLineWidth); if (nFullText != pItem->m_nUnicode && m_fTextWrap) // Doesn't fit, we have to word-wrap. { int nTruncated = pItem->m_nUnicode; // Just in case. // Now remove characters until we find a word-break character. while (pItem->m_nUnicode > 0 && pItem->m_Unicode[pItem->m_nUnicode] != ' ') { pItem->m_nUnicode--; } // If there are now word-break characters we truncate the text. if (pItem->m_nUnicode == 0) { pItem->m_nUnicode = nTruncated; } // Special case to avoid infinite loop if the box is very narrow. if (pItem->m_nUnicode == 0) { pItem->m_nUnicode = 1; } // We need to move the text we've cut off this line into a new line. int nNewWidth = nFullText - pItem->m_nUnicode; int nNewStart = pItem->m_nUnicode; // Remove any spaces at the start of the new section. while (nNewWidth != 0 && pItem->m_Unicode[nNewStart] == ' ') { nNewStart++; nNewWidth--; } if (nNewWidth != 0) { // Create a new line from the extra text. MHTextLine *pNewLine = new MHTextLine; theText.InsertAt(pNewLine, i + 1); // The first item on the new line is the rest of the text. MHTextItem *pNewItem = pItem->NewItem(); pNewLine->m_Items.Append(pNewItem); pNewItem->m_Unicode = pItem->m_Unicode.mid(nNewStart, nNewWidth); pNewItem->m_nUnicode = nNewWidth; // Move any remaining items, e.g. in a different colour, from this line onto the new line. while (pLine->m_Items.Size() > j + 1) { pNewLine->m_Items.Append(pLine->m_Items.GetAt(j + 1)); pLine->m_Items.RemoveAt(j + 1); } } // Remove any spaces at the end of the old section. If we don't do that and // we are centering or right aligning the text we'll get it wrong. while (pItem->m_nUnicode > 1 && pItem->m_Unicode[pItem->m_nUnicode-1] == ' ') { pItem->m_nUnicode--; } rect = m_pDisplay->GetBounds(pItem->m_Unicode, pItem->m_nUnicode); } pItem->m_Width = rect.width(); pLine->m_nLineWidth += rect.width(); if (rect.height() > pLine->m_nLineHeight) { pLine->m_nLineHeight = rect.height(); } if (rect.bottom() > pLine->m_nDescent) { pLine->m_nDescent = rect.bottom(); } } } // Now output the text. int yOffset = 0; // If there isn't space for all the lines we should drop extra lines. int nNumLines = theText.Size(); do { if (m_VertJ == End) { yOffset = m_nBoxHeight - nNumLines * lineSpace; } else if (m_VertJ == Centre) { yOffset = (m_nBoxHeight - nNumLines * lineSpace) / 2; } if (yOffset < 0) { nNumLines--; } } while (yOffset < 0); for (i = 0; i < nNumLines; i++) { MHTextLine *pLine = theText.GetAt(i); int xOffset = 0; if (m_HorizJ == End) { xOffset = m_nBoxWidth - pLine->m_nLineWidth; } else if (m_HorizJ == Centre) { xOffset = (m_nBoxWidth - pLine->m_nLineWidth) / 2; } for (int j = 0; j < pLine->m_Items.Size(); j++) { MHTextItem *pItem = pLine->m_Items.GetAt(j); // Tab across if necessary. xOffset = Tabs(xOffset, pItem->m_nTabCount); if (! pItem->m_Unicode.isEmpty()) // We may have blank lines. { m_pDisplay->AddText(xOffset, yOffset + (pLine->m_nLineHeight + lineSpace) / 2 - pLine->m_nDescent, pItem->m_Unicode.left(pItem->m_nUnicode), pItem->m_Colour); } xOffset += pItem->m_Width; } yOffset += lineSpace; if (yOffset + lineSpace > m_nBoxHeight) { break; } } // Clean up. for (int k = 0; k < theText.Size(); k++) { delete(theText.GetAt(k)); } }
// Process the action set and create the action values. void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) { // Depending on the caller we may have a tagged argument list or a sequence. for (int i = 0; i < p->GetArgCount(); i++) { MHParseNode *pElemAction = p->GetArgN(i); MHElemAction *pAction; switch (pElemAction->GetTagNo()) { case C_ACTIVATE: pAction = new MHActivate(":Activate", true); break; case C_ADD: pAction = new MHAdd; break; case C_ADD_ITEM: pAction = new MHAddItem; break; case C_APPEND: pAction = new MHAppend; break; case C_BRING_TO_FRONT: pAction = new MHBringToFront; break; case C_CALL: pAction = new MHCall(":Call", false); break; case C_CALL_ACTION_SLOT: pAction = new MHCallActionSlot; break; case C_CLEAR: pAction = new MHClear; break; case C_CLONE: pAction = new MHClone; break; case C_CLOSE_CONNECTION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ?? case C_DEACTIVATE: pAction = new MHActivate(":Deactivate", false); break; case C_DEL_ITEM: pAction = new MHDelItem; break; case C_DESELECT: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Button case C_DESELECT_ITEM: pAction = new MHDeselectItem; break; case C_DIVIDE: pAction = new MHDivide; break; case C_DRAW_ARC: pAction = new MHDrawArcSector(":DrawArc", false); break; case C_DRAW_LINE: pAction = new MHDrawLine; break; case C_DRAW_OVAL: pAction = new MHDrawOval; break; case C_DRAW_POLYGON: pAction = new MHDrawPoly(":DrawPolygon", true); break; case C_DRAW_POLYLINE: pAction = new MHDrawPoly(":DrawPolyline", false); break; case C_DRAW_RECTANGLE: pAction = new MHDrawRectangle; break; case C_DRAW_SECTOR: pAction = new MHDrawArcSector(":DrawSector", true); break; case C_FORK: pAction = new MHCall(":Fork", true); break; case C_GET_AVAILABILITY_STATUS: pAction = new MHGetAvailabilityStatus; break; case C_GET_BOX_SIZE: pAction = new MHGetBoxSize; break; case C_GET_CELL_ITEM: pAction = new MHGetCellItem; break; case C_GET_CURSOR_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_GET_ENGINE_SUPPORT: pAction = new MHGetEngineSupport; break; case C_GET_ENTRY_POINT: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // EntryField case C_GET_FILL_COLOUR: pAction = new MHGetFillColour; break; case C_GET_FIRST_ITEM: pAction = new MHGetFirstItem; break; case C_GET_HIGHLIGHT_STATUS: pAction = new MHGetHighlightStatus; break; case C_GET_INTERACTION_STATUS: pAction = new MHGetInteractionStatus; break; case C_GET_ITEM_STATUS: pAction = new MHGetItemStatus; break; case C_GET_LABEL: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break;// PushButton case C_GET_LAST_ANCHOR_FIRED: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break;// HyperText case C_GET_LINE_COLOUR: pAction = new MHGetLineColour; break; case C_GET_LINE_STYLE: pAction = new MHGetLineStyle; break; case C_GET_LINE_WIDTH: pAction = new MHGetLineWidth; break; case C_GET_LIST_ITEM: pAction = new MHGetListItem; break; case C_GET_LIST_SIZE: pAction = new MHGetListSize; break; case C_GET_OVERWRITE_MODE: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break;// ? case C_GET_PORTION: pAction = new MHGetPortion; break; case C_GET_POSITION: pAction = new MHGetPosition; break; case C_GET_RUNNING_STATUS: pAction = new MHGetRunningStatus; break; case C_GET_SELECTION_STATUS: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break;// ? case C_GET_SLIDER_VALUE: pAction = new MHGetSliderValue; break; case C_GET_TEXT_CONTENT: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break;// Text case C_GET_TEXT_DATA: pAction = new MHGetTextData; break; case C_GET_TOKEN_POSITION: pAction = new MHGetTokenPosition; break; case C_GET_VOLUME: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_LAUNCH: pAction = new MHLaunch; break; case C_LOCK_SCREEN: pAction = new MHLockScreen; break; case C_MODULO: pAction = new MHModulo; break; case C_MOVE: pAction = new MHMove; break; case C_MOVE_TO: pAction = new MHMoveTo; break; case C_MULTIPLY: pAction = new MHMultiply; break; case C_OPEN_CONNECTION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_PRELOAD: pAction = new MHPreload; break; case C_PUT_BEFORE: pAction = new MHPutBefore; break; case C_PUT_BEHIND: pAction = new MHPutBehind; break; case C_QUIT: pAction = new MHQuit; break; case C_READ_PERSISTENT: pAction = new MHPersistent(":ReadPersistent", true); break; case C_RUN: pAction = new MHRun; break; case C_SCALE_BITMAP: pAction = new MHScaleBitmap; break; case C_SCALE_VIDEO: pAction = new MHScaleVideo; break; case C_SCROLL_ITEMS: pAction = new MHScrollItems; break; case C_SELECT: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Button case C_SELECT_ITEM: pAction = new MHSelectItem; break; case C_SEND_EVENT: pAction = new MHSendEvent; break; case C_SEND_TO_BACK: pAction = new MHSendToBack; break; case C_SET_BOX_SIZE: pAction = new MHSetBoxSize; break; case C_SET_CACHE_PRIORITY: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_SET_COUNTER_END_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Stream case C_SET_COUNTER_POSITION: pAction = new MHSetCounterPosition; break; // Stream case C_SET_COUNTER_TRIGGER: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Stream case C_SET_CURSOR_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_SET_CURSOR_SHAPE: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_SET_DATA: pAction = new MHSetData; break; case C_SET_ENTRY_POINT: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // EntryField case C_SET_FILL_COLOUR: pAction = new MHSetFillColour; break; case C_SET_FIRST_ITEM: pAction = new MHSetFirstItem; break; case C_SET_FONT_REF: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Text case C_SET_HIGHLIGHT_STATUS: pAction = new MHSetHighlightStatus; break; case C_SET_INTERACTION_STATUS: pAction = new MHSetInteractionStatus; break; case C_SET_LABEL: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // PushButton case C_SET_LINE_COLOUR: pAction = new MHSetLineColour; break; case C_SET_LINE_STYLE: pAction = new MHSetLineStyle; break; case C_SET_LINE_WIDTH: pAction = new MHSetLineWidth; break; case C_SET_OVERWRITE_MODE: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // EntryField case C_SET_PALETTE_REF: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Visible case C_SET_PORTION: pAction = new MHSetPortion; break; case C_SET_POSITION: pAction = new MHSetPosition; break; case C_SET_SLIDER_VALUE: pAction = new MHSetSliderValue; break; case C_SET_SPEED: pAction = new MHSetSpeed; break; // ? case C_SET_TIMER: pAction = new MHSetTimer; break; case C_SET_TRANSPARENCY: pAction = new MHSetTransparency; break; case C_SET_VARIABLE: pAction = new MHSetVariable; break; case C_SET_VOLUME: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_SPAWN: pAction = new MHSpawn; break; case C_STEP: pAction = new MHStep; break; case C_STOP: pAction = new MHStop; break; case C_STORE_PERSISTENT: pAction = new MHPersistent(":StorePersistent", false); break; case C_SUBTRACT: pAction = new MHSubtract; break; case C_TEST_VARIABLE: pAction = new MHTestVariable; break; case C_TOGGLE: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Button case C_TOGGLE_ITEM: pAction = new MHToggleItem; break; case C_TRANSITION_TO: pAction = new MHTransitionTo; break; case C_UNLOAD: pAction = new MHUnload; break; case C_UNLOCK_SCREEN: pAction = new MHUnlockScreen; break; // UK MHEG added actions. case C_SET_BACKGROUND_COLOUR: pAction = new MHSetBackgroundColour; break; case C_SET_CELL_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ? case C_SET_INPUT_REGISTER: pAction = new MHSetInputRegister; break; case C_SET_TEXT_COLOUR: pAction = new MHSetTextColour; break; case C_SET_FONT_ATTRIBUTES: pAction = new MHSetFontAttributes; break; case C_SET_VIDEO_DECODE_OFFSET: pAction = new MHSetVideoDecodeOffset; break; case C_GET_VIDEO_DECODE_OFFSET: pAction = new MHGetVideoDecodeOffset; break; case C_GET_FOCUS_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // HyperText case C_SET_FOCUS_POSITION: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // HyperText case C_SET_BITMAP_DECODE_OFFSET: pAction = new MHSetBitmapDecodeOffset; break; case C_GET_BITMAP_DECODE_OFFSET: pAction = new MHGetBitmapDecodeOffset; break; case C_SET_SLIDER_PARAMETERS: pAction = new MHSetSliderParameters; break; // Added in ETSI ES 202 184 V2.1.1 (2010-01) case C_GET_COUNTER_POSITION: // Stream position pAction = new MHGetCounterPosition; break; case C_GET_COUNTER_MAX_POSITION: // Stream total size pAction = new MHGetCounterMaxPosition; break; default: MHLOG(MHLogWarning, QString("WARN Unknown action %1").arg(pElemAction->GetTagNo())); // Future proofing: ignore any actions that we don't know about. // Obviously these can only arise in the binary coding. pAction = NULL; } if (pAction) { Append(pAction); // Add to the sequence. pAction->Initialise(pElemAction, engine); } } }
MHUnimplementedAction(int nTag): MHElemAction(""), m_nTag(nTag) { MHLOG(MHLogWarning, QString("WARN Unimplemented action %1").arg(m_nTag) ); }
// Implement the SetVariable action. void MHContentRefVar::SetVariableValue(const MHUnion &value) { value.CheckType(MHUnion::U_ContentRef); m_Value.Copy(value.m_ContentRefVal); MHLOG(MHLogDetail, QString("Update %1 := %2").arg(m_ObjectReference.Printable()).arg(m_Value.Printable())); }
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; } } }
// Find out what we support. bool MHEngine::GetEngineSupport(const MHOctetString &feature) { QString csFeat = QString::fromUtf8((const char *)feature.Bytes(), feature.Size()); QStringList strings = csFeat.split(QRegExp("[\\(\\,\\)]")); MHLOG(MHLogNotifications, "NOTE GetEngineSupport " + csFeat); if (strings[0] == "ApplicationStacking" || strings[0] == "ASt") { return true; } // We're required to support cloning for Text, Bitmap and Rectangle. if (strings[0] == "Cloning" || strings[0] == "Clo") { return true; } if (strings[0] == "SceneCoordinateSystem" || strings[0] == "SCS") { if (strings.count() >= 3 && strings[1] == "720" && strings[2] == "576") { return true; } else { return false; } // I've also seen SceneCoordinateSystem(1,1) } if (strings[0] == "MultipleAudioStreams" || strings[0] == "MAS") { if (strings.count() >= 2 && (strings[1] == "0" || strings[1] == "1")) { return true; } else { return false; } } if (strings[0] == "MultipleVideoStreams" || strings[0] == "MVS") { if (strings.count() >= 2 && (strings[1] == "0" || strings[1] == "1")) { return true; } else { return false; } } // We're supposed to return true for all values of N if (strings[0] == "OverlappingVisibles" || strings[0] == "OvV") { return true; } if (strings[0] == "SceneAspectRatio" || strings[0] == "SAR") { if (strings.count() < 3) { return false; } else if ((strings[1] == "4" && strings[2] == "3") || (strings[1] == "16" && strings[2] == "9")) { return true; } else { return false; } } // We're supposed to support these at least. May also support(10,1440,1152) if (strings[0] == "VideoScaling" || strings[0] == "VSc") { if (strings.count() < 4 || strings[1] != "10") { return false; } else if ((strings[2] == "720" && strings[3] == "576") || (strings[2] == "360" && strings[3] == "288")) { return true; } else { return false; } } if (strings[0] == "BitmapScaling" || strings[0] == "BSc") { if (strings.count() < 4 || strings[1] != "2") { return false; } else if ((strings[2] == "720" && strings[3] == "576") || (strings[2] == "360" && strings[3] == "288")) { return true; } else { return false; } } // I think we only support the video fully on screen if (strings[0] == "VideoDecodeOffset" || strings[0] == "VDO") { if (strings.count() >= 3 && strings[1] == "10" && strings[1] == "0") { return true; } else { return false; } } // We support bitmaps that are partially off screen (don't we?) if (strings[0] == "BitmapDecodeOffset" || strings[0] == "BDO") { if (strings.count() >= 3 && strings[1] == "2" && (strings[2] == "0" || strings[2] == "1")) { return true; } else if (strings.count() >= 2 && (strings[1] == "4" || strings[1] == "6")) { return true; } else { return false; } } if (strings[0] == "UKEngineProfile" || strings[0] == "UniversalEngineProfile" || strings[0] == "UEP") { if (strings.count() < 2) { return false; } if (strings[1] == MHEGEngineProviderIdString) { return true; } if (strings[1] == m_Context->GetReceiverId()) { return true; } if (strings[1] == m_Context->GetDSMCCId()) { return true; } // The UK profile 1.06 seems a bit confused on this point. It is not clear whether // we are supposed to return true for UKEngineProfile(2) or not. if (strings[1] == "2") { return true; } else { return false; } } // InteractionChannelExtension. if (strings[0] == "ICProfile" || strings[0] == "ICP") { if (strings.count() != 2) return false; if (strings[1] == "0") return true; // // InteractionChannelExtension. if (strings[1] == "1") return false; // ICStreamingExtension. return false; } // Otherwise return false. return false; }