MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
Engine.cpp
Go to the documentation of this file.
1 /* Engine.cpp
2 
3  Copyright (C) David C. J. Matthews 2004, 2008 dm at prolingua.co.uk
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either version 2
8  of the License, or (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  Or, point your browser to http://www.gnu.org/copyleft/gpl.html
19 
20 */
21 
22 #include <QStringList>
23 #include <QRegExp>
24 
25 #include "Engine.h"
26 #include "ParseNode.h"
27 #include "ParseBinary.h"
28 #include "ParseText.h"
29 #include "Root.h"
30 #include "Groups.h"
31 #include "ASN1Codes.h"
32 #include "Logging.h"
33 #include "freemheg.h"
34 #include "Visible.h" // For MHInteractible
35 #include "Stream.h"
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <stdarg.h>
40 
41 // External creation function.
43 {
44  return new MHEngine(context);
45 }
46 
47 MHEngine::MHEngine(MHContext *context): m_Context(context)
48 {
49  m_fInTransition = false;
50  m_fBooting = true;
51  m_Interacting = 0;
52 }
53 
55 {
56  while (!m_ApplicationStack.isEmpty())
57  {
58  delete m_ApplicationStack.pop();
59  }
60 
61  while (!m_EventQueue.isEmpty())
62  {
63  delete m_EventQueue.dequeue();
64  }
65 
66  while (!m_ExternContentTable.isEmpty())
67  {
68  delete m_ExternContentTable.takeFirst();
69  }
70 }
71 
72 // Check for external content every 2 seconds.
73 #define CONTENT_CHECK_TIME 2000
74 
75 // This is the main loop of the engine.
77 {
78  // Request to boot or reboot
79  if (m_fBooting)
80  {
81  // Reset everything
82  while (!m_ApplicationStack.isEmpty())
83  {
84  delete m_ApplicationStack.pop();
85  }
86 
87  while (!m_EventQueue.isEmpty())
88  {
89  delete m_EventQueue.dequeue();
90  }
91 
92  while (!m_ExternContentTable.isEmpty())
93  {
94  delete m_ExternContentTable.takeFirst();
95  }
96 
97  m_LinkTable.clear();
98 
99  // UK MHEG applications boot from ~//a or ~//startup. Actually the initial
100  // object can also be explicitly given in the
101  MHObjectRef startObj;
102  startObj.m_nObjectNo = 0;
103  startObj.m_GroupId.Copy(MHOctetString("~//a"));
104 
105  // Launch will block until either it finds the appropriate object and
106  // begins the application or discovers that the file definitely isn't
107  // present in the carousel. It is possible that the object might appear
108  // if one of the containing directories is updated.
109  if (! Launch(startObj))
110  {
111  startObj.m_GroupId.Copy(MHOctetString("~//startup"));
112 
113  if (! Launch(startObj))
114  {
115  MHLOG(MHLogNotifications, "NOTE Engine auto-boot failed");
116  return -1;
117  }
118  }
119 
120  m_fBooting = false;
121  }
122 
123  int nNextTime = 0;
124 
125  do
126  {
127  // Check to see if we need to close.
128  if (m_Context->CheckStop())
129  {
130  return 0;
131  }
132 
133  // Run queued actions.
134  RunActions();
135  // Now the action stack is empty process the next asynchronous event.
136  // Processing one event may affect how subsequent events are handled.
137 
138  // Check to see if some files we're waiting for have arrived.
139  // This could result in ContentAvailable events.
141 
142  // Check the timers. This may result in timer events being raised.
143  nNextTime = CurrentScene() ? CurrentScene()->CheckTimers(this) : 0;
144 
145  if (CurrentApp())
146  {
147  // The UK MHEG profile allows applications to have timers.
148  int nAppTime = CurrentApp()->CheckTimers(this);
149 
150  if (nAppTime != 0 && (nNextTime == 0 || nAppTime < nNextTime))
151  {
152  nNextTime = nAppTime;
153  }
154  }
155 
156  if (! m_ExternContentTable.isEmpty())
157  {
158  // If we have an outstanding request for external content we need to set a timer.
159  if (nNextTime == 0 || nNextTime > CONTENT_CHECK_TIME)
160  {
161  nNextTime = CONTENT_CHECK_TIME;
162  }
163  }
164 
165  if (! m_EventQueue.isEmpty())
166  {
167  MHAsynchEvent *pEvent = m_EventQueue.dequeue();
168  MHLOG(MHLogLinks, QString("Asynchronous event dequeued - %1 from %2")
169  .arg(MHLink::EventTypeToString(pEvent->eventType))
170  .arg(pEvent->pEventSource->m_ObjectReference.Printable()));
171  CheckLinks(pEvent->pEventSource->m_ObjectReference, pEvent->eventType, pEvent->eventData);
172  delete pEvent;
173  }
174  }
175  while (! m_EventQueue.isEmpty() || ! m_ActionStack.isEmpty());
176 
177  // Redraw the display if necessary.
178  if (! m_redrawRegion.isEmpty())
179  {
181  m_redrawRegion = QRegion();
182  }
183 
184  return nNextTime;
185 }
186 
187 
188 // Convert the parse tree for an application or scene into an object node.
189 MHGroup *MHEngine::ParseProgram(QByteArray &text)
190 {
191  if (text.size() == 0)
192  {
193  return NULL;
194  }
195 
196  // Look at the first byte to decide whether this is text or binary. Binary
197  // files will begin with 0xA0 or 0xA1, text files with white space, comment ('/')
198  // or curly bracket.
199  // This is only there for testing: all downloaded objects will be in ASN1
200  unsigned char ch = text[0];
201  MHParseBase *parser = NULL;
202  MHParseNode *pTree = NULL;
203  MHGroup *pRes = NULL;
204 
205  if (ch >= 128)
206  {
207  parser = new MHParseBinary(text);
208  }
209  else
210  {
211  parser = new MHParseText(text);
212  }
213 
214  try
215  {
216  // Parse the binary or text.
217  pTree = parser->Parse();
218 
219  switch (pTree->GetTagNo()) // The parse node should be a tagged item.
220  {
221  case C_APPLICATION:
222  pRes = new MHApplication;
223  break;
224  case C_SCENE:
225  pRes = new MHScene;
226  break;
227  default:
228  pTree->Failure("Expected Application or Scene"); // throws exception.
229  }
230 
231  pRes->Initialise(pTree, this); // Convert the parse tree.
232  delete(pTree);
233  delete(parser);
234  }
235  catch (...)
236  {
237  delete(parser);
238  delete(pTree);
239  delete(pRes);
240  throw;
241  }
242 
243  return pRes;
244 }
245 
246 // Determine protocol for a file
248 static EProtocol PathProtocol(const QString& csPath)
249 {
250  if (csPath.isEmpty() || csPath.startsWith("DSM:") || csPath.startsWith("~"))
251  return kProtoDSM;
252  if (csPath.startsWith("hybrid:"))
253  return kProtoHybrid;
254  if (csPath.startsWith("http:") || csPath.startsWith("https:"))
255  return kProtoHTTP;
256  if (csPath.startsWith("CI:"))
257  return kProtoCI;
258 
259  int firstColon = csPath.indexOf(':'), firstSlash = csPath.indexOf('/');
260  if (firstColon > 0 && firstSlash > 0 && firstColon < firstSlash)
261  return kProtoUnknown;
262 
263  return kProtoDSM;
264 }
265 
266 // Launch and Spawn
267 bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn)
268 {
269  if (m_fInTransition)
270  {
271  MHLOG(MHLogWarning, "WARN Launch during transition - ignoring");
272  return false;
273  }
274 
275  if (target.m_GroupId.Size() == 0) return false; // No file name.
276  QString csPath = GetPathName(target.m_GroupId); // Get path relative to root.
277 
278  // Check that the file exists before we commit to the transition.
279  // This may block if we cannot be sure whether the object is present.
280  QByteArray text;
281  if (! m_Context->GetCarouselData(csPath, text))
282  {
283  if (!m_fBooting)
284  EngineEvent(2); // GroupIDRefError
285  return false;
286  }
287 
288  MHApplication *pProgram = (MHApplication*)ParseProgram(text);
289  if (! pProgram)
290  {
291  MHLOG(MHLogWarning, "Empty application");
292  return false;
293  }
294  if (! pProgram->m_fIsApp)
295  {
296  MHLOG(MHLogWarning, "Expected an application");
297  delete pProgram;
298  return false;
299  }
300  if ((__mhlogoptions & MHLogScenes) && __mhlogStream != 0) // Print it so we know what's going on.
301  {
302  pProgram->PrintMe(__mhlogStream, 0);
303  }
304 
305  // Clear the action queue of anything pending.
306  m_ActionStack.clear();
307 
308  m_fInTransition = true; // Starting a transition
309 
310  try
311  {
312  if (CurrentApp())
313  {
314  if (fIsSpawn) // Run the CloseDown actions.
315  {
316  AddActions(CurrentApp()->m_CloseDown);
317  RunActions();
318  }
319 
320  if (CurrentScene())
321  {
322  CurrentScene()->Destruction(this);
323  }
324 
325  CurrentApp()->Destruction(this);
326 
327  if (!fIsSpawn)
328  {
329  delete m_ApplicationStack.pop(); // Pop and delete the current app.
330  }
331  }
332 
333  // Save the path we use for this app.
334  pProgram->m_Path = csPath; // Record the path
335  int nPos = pProgram->m_Path.lastIndexOf('/');
336 
337  if (nPos < 0)
338  {
339  pProgram->m_Path = "";
340  }
341  else
342  {
343  pProgram->m_Path = pProgram->m_Path.left(nPos);
344  }
345 
346  // Have now got the application.
347  m_ApplicationStack.push(pProgram);
348 
349  // This isn't in the standard as far as I can tell but we have to do this because
350  // we may have events referring to the old application.
351  while (!m_EventQueue.isEmpty())
352  {
353  delete m_EventQueue.dequeue();
354  }
355 
356  // Activate the application. ....
357  CurrentApp()->Activation(this);
358  m_fInTransition = false; // The transition is complete
359  return true;
360  }
361  catch (...)
362  {
363  m_fInTransition = false; // The transition is complete
364  return false;
365  }
366 }
367 
369 {
370  if (m_fInTransition)
371  {
372  MHLOG(MHLogWarning, "WARN Quit during transition - ignoring");
373  return;
374  }
375 
376  m_fInTransition = true; // Starting a transition
377 
378  if (CurrentScene())
379  {
380  CurrentScene()->Destruction(this);
381  }
382 
383  CurrentApp()->Destruction(this);
384 
385  // This isn't in the standard as far as I can tell but we have to do this because
386  // we may have events referring to the old application.
387  while (!m_EventQueue.isEmpty())
388  {
389  delete m_EventQueue.dequeue();
390  }
391 
392  delete m_ApplicationStack.pop();
393 
394  // If the stack is now empty we return to boot mode.
395  if (m_ApplicationStack.isEmpty())
396  {
397  m_fBooting = true;
398  }
399  else
400  {
401  CurrentApp()->m_fRestarting = true;
402  CurrentApp()->Activation(this); // This will do any OnRestart actions.
403  // Note - this doesn't activate the previously active scene.
404  }
405 
406  m_fInTransition = false; // The transition is complete
407 }
408 
410 {
411  int i;
412 
413  if (m_fInTransition)
414  {
415  // TransitionTo is not allowed in OnStartUp or OnCloseDown actions.
416  MHLOG(MHLogWarning, "WARN TransitionTo during transition - ignoring");
417  return;
418  }
419 
420  if (target.m_GroupId.Size() == 0)
421  {
422  return; // No file name.
423  }
424 
425  QString csPath = GetPathName(target.m_GroupId);
426 
427  // Check that the file exists before we commit to the transition.
428  // This may block if we cannot be sure whether the object is present.
429  QByteArray text;
430  if (! m_Context->GetCarouselData(csPath, text)) {
431  EngineEvent(2); // GroupIDRefError
432  return;
433  }
434 
435  // Parse and run the file.
436  MHGroup *pProgram = ParseProgram(text);
437 
438  if (!pProgram )
439  MHERROR("Empty scene");
440 
441  if (pProgram->m_fIsApp)
442  {
443  delete pProgram;
444  MHERROR("Expected a scene");
445  }
446 
447  // Clear the action queue of anything pending.
448  m_ActionStack.clear();
449 
450  // At this point we have managed to load the scene.
451  // Deactivate any non-shared ingredients in the application.
452  MHApplication *pApp = CurrentApp();
453 
454  for (i = pApp->m_Items.Size(); i > 0; i--)
455  {
456  MHIngredient *pItem = pApp->m_Items.GetAt(i - 1);
457 
458  if (! pItem->IsShared())
459  {
460  pItem->Deactivation(this); // This does not remove them from the display stack.
461  }
462  }
463 
464  m_fInTransition = true; // TransitionTo etc are not allowed.
465 
466  if (pApp->m_pCurrentScene)
467  {
468  pApp->m_pCurrentScene->Deactivation(this); // This may involve a call to RunActions
469  pApp->m_pCurrentScene->Destruction(this);
470  }
471 
472  // Everything that belongs to the previous scene should have been removed from the display stack.
473 
474  // At this point we may have added actions to the queue as a result of synchronous
475  // events during the deactivation.
476 
477  // Remove any events from the asynch event queue unless they derive from
478  // the application itself or a shared ingredient.
479  MHAsynchEvent *pEvent;
480  QQueue<MHAsynchEvent *>::iterator it = m_EventQueue.begin();
481 
482  while (it != m_EventQueue.end())
483  {
484  pEvent = *it;
485 
486  if (!pEvent->pEventSource->IsShared())
487  {
488  delete pEvent;
489  it = m_EventQueue.erase(it);
490  }
491  else
492  {
493  ++it;
494  }
495  }
496 
497  // Can now actually delete the old scene.
498  if (pApp->m_pCurrentScene)
499  {
500  delete(pApp->m_pCurrentScene);
501  pApp->m_pCurrentScene = NULL;
502  }
503 
504  m_Interacting = 0;
505 
506  // Switch to the new scene.
507  CurrentApp()->m_pCurrentScene = static_cast< MHScene* >(pProgram);
508  SetInputRegister(CurrentScene()->m_nEventReg);
509  m_redrawRegion = QRegion(0, 0, CurrentScene()->m_nSceneCoordX, CurrentScene()->m_nSceneCoordY); // Redraw the whole screen
510 
511  if ((__mhlogoptions & MHLogScenes) && __mhlogStream != 0) // Print it so we know what's going on.
512  {
513  pProgram->PrintMe(__mhlogStream, 0);
514  }
515 
516  pProgram->Preparation(this);
517  pProgram->Activation(this);
518  m_fInTransition = false; // The transition is complete
519 }
520 
522 {
523  m_Context->SetInputRegister(nReg); // Enable the appropriate buttons
524 }
525 
526 // Create a canonical path name. The rules are given in the UK MHEG document.
528 {
529  if (str.Size() == 0)
530  return QString();
531 
532  QString csPath = QString::fromUtf8((const char *)str.Bytes(), str.Size());
533  switch (PathProtocol(csPath))
534  {
535  default:
536  case kProtoUnknown:
537  case kProtoHybrid:
538  case kProtoHTTP:
539  case kProtoCI:
540  return csPath;
541  case kProtoDSM:
542  break;
543  }
544 
545  if (csPath.startsWith("DSM:"))
546  csPath = csPath.mid(4); // Remove DSM:
547  else if (csPath.startsWith("~"))
548  csPath = csPath.mid(1); // Remove ~
549  if (!csPath.startsWith("//"))
550  {
551  // Add the current application's path name
552  if (CurrentApp())
553  {
554  csPath = CurrentApp()->m_Path + csPath;
555  }
556  }
557 
558  // Remove any occurrences of x/../
559  int nPos;
560 
561  while ((nPos = csPath.indexOf("/../")) >= 0)
562  {
563  int nEnd = nPos + 4;
564 
565  while (nPos >= 1 && csPath[nPos-1] != '/')
566  {
567  nPos--;
568  }
569 
570  csPath = csPath.left(nPos) + csPath.mid(nEnd);
571  }
572 
573  return csPath;
574 }
575 
576 // Look up an object. In most cases we just want to fail if the object isn't found.
577 MHRoot *MHEngine::FindObject(const MHObjectRef &oRef, bool failOnNotFound)
578 {
579  // It should match either the application or the scene.
580  MHGroup *pSearch = NULL;
581  MHGroup *pScene = CurrentScene(), *pApp = CurrentApp();
582 
583  if (pScene && GetPathName(pScene->m_ObjectReference.m_GroupId) == GetPathName(oRef.m_GroupId))
584  {
585  pSearch = pScene;
586  }
587  else if (pApp && GetPathName(pApp->m_ObjectReference.m_GroupId) == GetPathName(oRef.m_GroupId))
588  {
589  pSearch = pApp;
590  }
591 
592  if (pSearch)
593  {
594  MHRoot *pItem = pSearch->FindByObjectNo(oRef.m_nObjectNo);
595 
596  if (pItem)
597  {
598  return pItem;
599  }
600  }
601 
602  if (failOnNotFound)
603  {
604  // I've seen at least one case where MHEG code has quite deliberately referred to
605  // an object that may or may not exist at a particular time.
606  // Another case was a call to CallActionSlot with an object reference variable
607  // that had been initialised to zero.
608  MHLOG(MHLogWarning, QString("WARN Reference %1 not found").arg(oRef.m_nObjectNo));
609  throw "FindObject failed";
610  }
611 
612  return NULL; // If we don't generate an error.
613 }
614 
615 // Run queued actions.
617 {
618  while (! m_ActionStack.isEmpty())
619  {
620  // Remove the first action.
621  MHElemAction *pAction = m_ActionStack.pop();
622 
623  // Run it. If it fails and throws an exception catch it and continue with the next.
624  try
625  {
626  if ((__mhlogoptions & MHLogActions) && __mhlogStream != 0) // Debugging
627  {
628  fprintf(__mhlogStream, "[freemheg] Action - ");
629  pAction->PrintMe(__mhlogStream, 0);
630  fflush(__mhlogStream);
631  }
632 
633  pAction->Perform(this);
634  }
635  catch (char const *)
636  {
637  }
638  }
639 }
640 
641 // Called when an event is triggered. Either queues the event or finds a link that matches.
642 void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion &evData)
643 {
644  MHLOG(MHLogLinks, QString("Event - %1 from %2")
645  .arg(MHLink::EventTypeToString(ev)).arg(pSource->m_ObjectReference.Printable()));
646 
647  switch (ev)
648  {
650  case EventHeadItems:
651  case EventHighlightOff:
652  case EventHighlightOn:
653  case EventIsAvailable:
654  case EventIsDeleted:
655  case EventIsDeselected:
656  case EventIsRunning:
657  case EventIsSelected:
658  case EventIsStopped:
659  case EventItemDeselected:
660  case EventItemSelected:
662  case EventTailItems:
663  case EventTestEvent:
664  case EventTokenMovedFrom:
665  case EventTokenMovedTo:
666  // Synchronous events. Fire any links that are waiting.
667  // The UK MHEG describes this as the preferred interpretation. We are checking the link
668  // at the time we generate the event rather than queuing the synchronous events until
669  // this elementary action is complete. That matters if we are processing an elementary action
670  // which will activate or deactivate links.
671  CheckLinks(pSource->m_ObjectReference, ev, evData);
672  break;
673  case EventAnchorFired:
674  case EventAsyncStopped:
676  case EventCounterTrigger:
677  case EventCursorEnter:
678  case EventCursorLeave:
679  case EventEngineEvent:
680  case EventEntryFieldFull:
682  case EventStreamEvent:
683  case EventStreamPlaying:
684  case EventStreamStopped:
685  case EventTimerFired:
686  case EventUserInput:
687  case EventFocusMoved: // UK MHEG. Generated by HyperText class
688  case EventSliderValueChanged: // UK MHEG. Generated by Slider class
689  default:
690  {
691  // Asynchronous events. Add to the event queue.
692  MHAsynchEvent *pEvent = new MHAsynchEvent;
693  pEvent->pEventSource = pSource;
694  pEvent->eventType = ev;
695  pEvent->eventData = evData;
696  m_EventQueue.enqueue(pEvent);
697  }
698  break;
699  }
700 }
701 
702 
703 // TO CHECK: If two actions both depend on the same event is the order in which the links are
704 // searched defined? This processes items in the order in which they were activated rather
705 // than their static position in the group.
706 
707 // Check all the links in the application and scene and fire any that match this event.
708 void MHEngine::CheckLinks(const MHObjectRef &sourceRef, enum EventType ev, const MHUnion &un)
709 {
710  for (int i = 0; i < m_LinkTable.size(); i++)
711  {
712  m_LinkTable.at(i)->MatchEvent(sourceRef, ev, un, this);
713  }
714 }
715 
716 // Add and remove links to and from the active link table.
718 {
719  m_LinkTable.append(pLink);
720 }
721 
723 {
724  m_LinkTable.removeAll(pLink);
725 }
726 
727 // Called when a link fires to add the actions to the action stack.
729 {
730  // Put them on the stack in reverse order so that we will pop the first.
731  for (int i = actions.Size(); i > 0; i--)
732  {
733  m_ActionStack.push(actions.GetAt(i - 1));
734  }
735 }
736 
737 // Add a visible to the display stack if it isn't already there.
739 {
740  if (CurrentApp()->FindOnStack(pVis) != -1)
741  {
742  return; // Return if it's already there.
743  }
744 
746  Redraw(pVis->GetVisibleArea()); // Request a redraw
747 }
748 
749 // Remove a visible from the display stack if it is there.
751 {
752  int nPos = CurrentApp()->FindOnStack(pVis);
753 
754  if (nPos == -1)
755  {
756  return;
757  }
758 
760  Redraw(pVis->GetVisibleArea()); // Request a redraw
761 }
762 
763 // Functions to alter the Z-order.
765 {
766  int nPos = CurrentApp()->FindOnStack(p);
767 
768  if (nPos == -1)
769  {
770  return; // If it's not there do nothing
771  }
772 
773  MHVisible *pVis = (MHVisible *)p; // Can now safely cast it.
774  CurrentApp()->m_DisplayStack.RemoveAt(nPos); // Remove it from its present posn
775  CurrentApp()->m_DisplayStack.Append((MHVisible *)pVis); // Push it on the top.
776  Redraw(pVis->GetVisibleArea()); // Request a redraw
777 }
778 
780 {
781  int nPos = CurrentApp()->FindOnStack(p);
782 
783  if (nPos == -1)
784  {
785  return; // If it's not there do nothing
786  }
787 
788  MHVisible *pVis = (MHVisible *)p; // Can now safely cast it.
789  CurrentApp()->m_DisplayStack.RemoveAt(nPos); // Remove it from its present posn
790  CurrentApp()->m_DisplayStack.InsertAt(pVis, 0); // Put it on the bottom.
791  Redraw(pVis->GetVisibleArea()); // Request a redraw
792 }
793 
794 void MHEngine::PutBefore(const MHRoot *p, const MHRoot *pRef)
795 {
796  int nPos = CurrentApp()->FindOnStack(p);
797 
798  if (nPos == -1)
799  {
800  return; // If it's not there do nothing
801  }
802 
803  MHVisible *pVis = (MHVisible *)p; // Can now safely cast it.
804  int nRef = CurrentApp()->FindOnStack(pRef);
805 
806  if (nRef == -1)
807  {
808  return; // If the reference visible isn't there do nothing.
809  }
810 
812 
813  if (nRef >= nPos)
814  {
815  nRef--; // The position of the reference may have shifted
816  }
817 
818  CurrentApp()->m_DisplayStack.InsertAt(pVis, nRef + 1);
819  // Redraw the area occupied by the moved item. We might be able to reduce
820  // the area to be redrawn by looking at the way it is affected by other items
821  // in the stack. We could also see whether it's currently active.
822  Redraw(pVis->GetVisibleArea()); // Request a redraw
823 }
824 
825 void MHEngine::PutBehind(const MHRoot *p, const MHRoot *pRef)
826 {
827  int nPos = CurrentApp()->FindOnStack(p);
828 
829  if (nPos == -1)
830  {
831  return; // If it's not there do nothing
832  }
833 
834  int nRef = CurrentApp()->FindOnStack(pRef);
835 
836  if (nRef == -1)
837  {
838  return; // If the reference visible isn't there do nothing.
839  }
840 
841  MHVisible *pVis = (MHVisible *)p; // Can now safely cast it.
843 
844  if (nRef >= nPos)
845  {
846  nRef--; // The position of the reference may have shifted
847  }
848 
849  CurrentApp()->m_DisplayStack.InsertAt((MHVisible *)pVis, nRef); // Shift the reference and anything above up.
850  Redraw(pVis->GetVisibleArea()); // Request a redraw
851 }
852 
853 // Draw a region of the screen. This attempts to minimise the drawing by eliminating items
854 // that are completely obscured by items above them. We have to take into account the
855 // transparency of items since items higher up the stack may be semi-transparent.
856 void MHEngine::DrawRegion(QRegion toDraw, int nStackPos)
857 {
858  if (toDraw.isEmpty())
859  {
860  return; // Nothing left to draw.
861  }
862 
863  while (nStackPos >= 0)
864  {
865  MHVisible *pItem = CurrentApp()->m_DisplayStack.GetAt(nStackPos);
866  // Work out how much of the area we want to draw is included in this visible.
867  // The visible area will be empty if the item is transparent or not active.
868  QRegion drawArea = pItem->GetVisibleArea() & toDraw;
869 
870  if (! drawArea.isEmpty()) // It contributes something.
871  {
872  // Remove the opaque area of this item from the region we have left.
873  // If this item is (semi-)transparent this will not remove anything.
874  QRegion newDraw = toDraw - pItem->GetOpaqueArea();
875  DrawRegion(newDraw, nStackPos - 1); // Do the items further down if any.
876  // Now we've drawn anything below this we can draw this item on top.
877  pItem->Display(this);
878  return;
879  }
880 
881  nStackPos--;
882  }
883 
884  // We've drawn all the visibles and there's still some undrawn area.
885  // Fill it with black.
886  m_Context->DrawBackground(toDraw);
887 }
888 
889 // Redraw an area of the display. This will be called via the context from Redraw.
890 void MHEngine::DrawDisplay(QRegion toDraw)
891 {
892  if (m_fBooting)
893  {
894  return;
895  }
896 
897  int nTopStack = CurrentApp() == NULL ? -1 : CurrentApp()->m_DisplayStack.Size() - 1;
898  DrawRegion(toDraw, nTopStack);
899 }
900 
901 // An area of the screen needs to be redrawn. We simply remember this and redraw it
902 // in one go when the timer expires.
903 void MHEngine::Redraw(QRegion region)
904 {
905  m_redrawRegion += region;
906 }
907 
908 // Called to decrement the lock count.
910 {
911  if (CurrentApp()->m_nLockCount > 0)
912  {
914  }
915 }
916 
917 
918 // Called from the windowing application, this generates a user event as the result of a button push.
920 {
921  MHScene *pScene = CurrentScene();
922 
923  if (! pScene)
924  {
925  return;
926  }
927 
928  // Various keys generate engine events as well as user events.
929  // These are generated before the user events and even if there
930  // is an interactible.
931  switch (nCode)
932  {
933  case 104:
934  case 105: // Text key
935  EventTriggered(pScene, EventEngineEvent, 4);
936  break;
937  case 16: // Text Exit/Cancel key
938  case 100: // Red
939  case 101: // Green
940  case 102: // Yellow
941  case 103: // Blue
942  case 300: // EPG
943  EventTriggered(pScene, EventEngineEvent, nCode);
944  break;
945  }
946 
947  // If we are interacting with an interactible send the key
948  // there otherwise generate a user event.
949  if (m_Interacting)
950  {
951  m_Interacting->KeyEvent(this, nCode);
952  }
953  else
954  {
955  EventTriggered(pScene, EventUserInput, nCode);
956  }
957 }
958 
959 void MHEngine::EngineEvent(int nCode)
960 {
961  if (CurrentApp())
963  else if (!m_fBooting)
964  MHLOG(MHLogWarning, QString("WARN EngineEvent %1 but no app").arg(nCode));
965 }
966 
967 void MHEngine::StreamStarted(MHStream *stream, bool bStarted)
968 {
970 }
971 
972 // Called by an ingredient wanting external content.
974 {
975  // It seems that some MHEG applications contain active ingredients with empty contents
976  // This isn't correct but we simply ignore that.
977  if (! pRequester->m_ContentRef.IsSet())
978  {
979  return;
980  }
981 
982  // Remove any existing content requests for this ingredient.
983  CancelExternalContentRequest(pRequester);
984 
985  QString csPath = GetPathName(pRequester->m_ContentRef.m_ContentRef);
986 
987  if (csPath.isEmpty())
988  {
989  MHLOG(MHLogWarning, "RequestExternalContent empty path");
990  return;
991  }
992 
993  if (m_Context->CheckCarouselObject(csPath))
994  {
995  // Available now - pass it to the ingredient.
996  QByteArray text;
997  if (m_Context->GetCarouselData(csPath, text))
998  {
999  // If the content is not recognized catch the exception and continue
1000  try
1001  {
1002  pRequester->ContentArrived(
1003  reinterpret_cast< const unsigned char * >(text.constData()),
1004  text.size(), this);
1005  }
1006  catch (char const *)
1007  {}
1008  }
1009  else
1010  {
1011  MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2")
1012  .arg(pRequester->m_ObjectReference.Printable()).arg(csPath));
1013  if (kProtoHTTP == PathProtocol(csPath))
1014  EngineEvent(203); // 203=RemoteNetworkError if 404 reply
1015  EngineEvent(3); // ContentRefError
1016  }
1017  }
1018  else
1019  {
1020  // Need to record this and check later.
1021  MHLOG(MHLogNotifications, QString("Waiting for %1 <= %2")
1022  .arg(pRequester->m_ObjectReference.Printable()).arg(csPath.left(128)) );
1023  MHExternContent *pContent = new MHExternContent;
1024  pContent->m_FileName = csPath;
1025  pContent->m_pRequester = pRequester;
1026  pContent->m_time.start();
1027  m_ExternContentTable.append(pContent);
1028  }
1029 }
1030 
1031 // Remove any pending requests from the queue.
1033 {
1034  QList<MHExternContent *>::iterator it = m_ExternContentTable.begin();
1035  MHExternContent *pContent;
1036 
1037  while (it != m_ExternContentTable.end())
1038  {
1039  pContent = *it;
1040 
1041  if (pContent->m_pRequester == pRequester)
1042  {
1043  MHLOG(MHLogNotifications, QString("Cancelled wait for %1")
1044  .arg(pRequester->m_ObjectReference.Printable()) );
1045  it = m_ExternContentTable.erase(it);
1046  delete pContent;
1047  return;
1048  }
1049  else
1050  {
1051  ++it;
1052  }
1053  }
1054 }
1055 
1056 // See if we can satisfy any of the outstanding requests.
1058 {
1059  QList<MHExternContent*>::iterator it = m_ExternContentTable.begin();
1060  while (it != m_ExternContentTable.end())
1061  {
1062  MHExternContent *pContent = *it;
1063  if (m_Context->CheckCarouselObject(pContent->m_FileName))
1064  {
1065  // Remove from the list.
1066  it = m_ExternContentTable.erase(it);
1067 
1068  QByteArray text;
1069  if (m_Context->GetCarouselData(pContent->m_FileName, text))
1070  {
1071  MHLOG(MHLogNotifications, QString("Received %1 len %2")
1072  .arg(pContent->m_pRequester->m_ObjectReference.Printable())
1073  .arg(text.size()) );
1074  // If the content is not recognized catch the exception and continue
1075  try
1076  {
1077  pContent->m_pRequester->ContentArrived(
1078  reinterpret_cast< const unsigned char * >(text.constData()),
1079  text.size(), this);
1080  }
1081  catch (char const *)
1082  {}
1083  }
1084  else
1085  {
1086  MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2")
1087  .arg(pContent->m_pRequester->m_ObjectReference.Printable())
1088  .arg(pContent->m_FileName));
1089  if (kProtoHTTP == PathProtocol(pContent->m_FileName))
1090  EngineEvent(203); // 203=RemoteNetworkError if 404 reply
1091  EngineEvent(3); // ContentRefError
1092  }
1093 
1094  delete pContent;
1095  }
1096  else if (pContent->m_time.elapsed() > 60000) // TODO Get this from carousel
1097  {
1098  // Remove from the list.
1099  it = m_ExternContentTable.erase(it);
1100 
1101  MHLOG(MHLogWarning, QString("WARN File timed out %1 <= %2")
1102  .arg(pContent->m_pRequester->m_ObjectReference.Printable())
1103  .arg(pContent->m_FileName));
1104 
1105  if (kProtoHTTP == PathProtocol(pContent->m_FileName))
1106  EngineEvent(203); // 203=RemoteNetworkError if 404 reply
1107  EngineEvent(3); // ContentRefError
1108 
1109  delete pContent;
1110  }
1111  else
1112  {
1113  ++it;
1114  }
1115  }
1116 }
1117 
1118 bool MHEngine::LoadStorePersistent(bool fIsLoad, const MHOctetString &fileName, const MHSequence<MHObjectRef *> &variables)
1119 {
1120  // See if there is an entry there already.
1121  MHPSEntry *pEntry = NULL;
1122  int i;
1123 
1124  for (i = 0; i < m_PersistentStore.Size(); i++)
1125  {
1126  pEntry = m_PersistentStore.GetAt(i);
1127 
1128  if (pEntry->m_FileName.Equal(fileName))
1129  {
1130  break;
1131  }
1132  }
1133 
1134  if (i == m_PersistentStore.Size()) // Not there.
1135  {
1136  // If we're loading then we've failed.
1137  if (fIsLoad)
1138  {
1139  return false;
1140  }
1141 
1142  // If we're storing we make a new entry.
1143  pEntry = new MHPSEntry;
1144  pEntry->m_FileName.Copy(fileName);
1145  m_PersistentStore.Append(pEntry);
1146  }
1147 
1148  if (fIsLoad) // Copy the data into the variables.
1149  {
1150  // Check that we have sufficient data before we continue?
1151  if (pEntry->m_Data.Size() < variables.Size())
1152  {
1153  return false;
1154  }
1155 
1156  for (i = 0; i < variables.Size(); i++)
1157  {
1158  FindObject(*(variables.GetAt(i)))->SetVariableValue(*(pEntry->m_Data.GetAt(i)));
1159  }
1160  }
1161 
1162  else // Get the data from the variables into the store.
1163  {
1164  // Remove any existing data.
1165  while (pEntry->m_Data.Size() != 0)
1166  {
1167  pEntry->m_Data.RemoveAt(0);
1168  }
1169 
1170  // Set the store to the values.
1171  for (i = 0; i < variables.Size(); i++)
1172  {
1173  MHUnion *pValue = new MHUnion;
1174  pEntry->m_Data.Append(pValue);
1175  FindObject(*(variables.GetAt(i)))->GetVariableValue(*pValue, this);
1176  }
1177  }
1178 
1179  return true;
1180 }
1181 
1182 // Find out what we support.
1184 {
1185  QString csFeat = QString::fromUtf8((const char *)feature.Bytes(), feature.Size());
1186  QStringList strings = csFeat.split(QRegExp("[\\(\\,\\)]"));
1187 
1188  MHLOG(MHLogNotifications, "NOTE GetEngineSupport " + csFeat);
1189 
1190  if (strings[0] == "ApplicationStacking" || strings[0] == "ASt")
1191  {
1192  return true;
1193  }
1194 
1195  // We're required to support cloning for Text, Bitmap and Rectangle.
1196  if (strings[0] == "Cloning" || strings[0] == "Clo")
1197  {
1198  return true;
1199  }
1200 
1201  if (strings[0] == "SceneCoordinateSystem" || strings[0] == "SCS")
1202  {
1203  if (strings.count() >= 3 && strings[1] == "720" && strings[2] == "576")
1204  {
1205  return true;
1206  }
1207  else
1208  {
1209  return false;
1210  }
1211 
1212  // I've also seen SceneCoordinateSystem(1,1)
1213  }
1214 
1215  if (strings[0] == "MultipleAudioStreams" || strings[0] == "MAS")
1216  {
1217  if (strings.count() >= 2 && (strings[1] == "0" || strings[1] == "1"))
1218  {
1219  return true;
1220  }
1221  else
1222  {
1223  return false;
1224  }
1225  }
1226 
1227  if (strings[0] == "MultipleVideoStreams" || strings[0] == "MVS")
1228  {
1229  if (strings.count() >= 2 && (strings[1] == "0" || strings[1] == "1"))
1230  {
1231  return true;
1232  }
1233  else
1234  {
1235  return false;
1236  }
1237  }
1238 
1239  // We're supposed to return true for all values of N
1240  if (strings[0] == "OverlappingVisibles" || strings[0] == "OvV")
1241  {
1242  return true;
1243  }
1244 
1245  if (strings[0] == "SceneAspectRatio" || strings[0] == "SAR")
1246  {
1247  if (strings.count() < 3)
1248  {
1249  return false;
1250  }
1251  else if ((strings[1] == "4" && strings[2] == "3") || (strings[1] == "16" && strings[2] == "9"))
1252  {
1253  return true;
1254  }
1255  else
1256  {
1257  return false;
1258  }
1259  }
1260 
1261  // We're supposed to support these at least. May also support(10,1440,1152)
1262  if (strings[0] == "VideoScaling" || strings[0] == "VSc")
1263  {
1264  if (strings.count() < 4 || strings[1] != "10")
1265  {
1266  return false;
1267  }
1268  else if ((strings[2] == "720" && strings[3] == "576") || (strings[2] == "360" && strings[3] == "288"))
1269  {
1270  return true;
1271  }
1272  else
1273  {
1274  return false;
1275  }
1276  }
1277 
1278  if (strings[0] == "BitmapScaling" || strings[0] == "BSc")
1279  {
1280  if (strings.count() < 4 || strings[1] != "2")
1281  {
1282  return false;
1283  }
1284  else if ((strings[2] == "720" && strings[3] == "576") || (strings[2] == "360" && strings[3] == "288"))
1285  {
1286  return true;
1287  }
1288  else
1289  {
1290  return false;
1291  }
1292  }
1293 
1294  // I think we only support the video fully on screen
1295  if (strings[0] == "VideoDecodeOffset" || strings[0] == "VDO")
1296  {
1297  if (strings.count() >= 3 && strings[1] == "10" && strings[1] == "0")
1298  {
1299  return true;
1300  }
1301  else
1302  {
1303  return false;
1304  }
1305  }
1306 
1307  // We support bitmaps that are partially off screen (don't we?)
1308  if (strings[0] == "BitmapDecodeOffset" || strings[0] == "BDO")
1309  {
1310  if (strings.count() >= 3 && strings[1] == "2" && (strings[2] == "0" || strings[2] == "1"))
1311  {
1312  return true;
1313  }
1314  else if (strings.count() >= 2 && (strings[1] == "4" || strings[1] == "6"))
1315  {
1316  return true;
1317  }
1318  else
1319  {
1320  return false;
1321  }
1322  }
1323 
1324  if (strings[0] == "UKEngineProfile" || strings[0] == "UniversalEngineProfile" || strings[0] == "UEP")
1325  {
1326  if (strings.count() < 2)
1327  {
1328  return false;
1329  }
1330 
1331  if (strings[1] == MHEGEngineProviderIdString)
1332  {
1333  return true;
1334  }
1335 
1336  if (strings[1] == m_Context->GetReceiverId())
1337  {
1338  return true;
1339  }
1340 
1341  if (strings[1] == m_Context->GetDSMCCId())
1342  {
1343  return true;
1344  }
1345 
1346  // The UK profile 1.06 seems a bit confused on this point. It is not clear whether
1347  // we are supposed to return true for UKEngineProfile(2) or not.
1348  if (strings[1] == "2")
1349  {
1350  return true;
1351  }
1352  else
1353  {
1354  return false;
1355  }
1356  }
1357 
1358  // InteractionChannelExtension.
1359  if (strings[0] == "ICProfile" || strings[0] == "ICP") {
1360  if (strings.count() != 2) return false;
1361  if (strings[1] == "0")
1362  return true; // // InteractionChannelExtension.
1363  if (strings[1] == "1")
1364  return false; // ICStreamingExtension.
1365  return false;
1366  }
1367 
1368  // Otherwise return false.
1369  return false;
1370 }
1371 
1372 // Get the various defaults. These are extracted from the current app or the (UK) MHEG defaults.
1374 {
1375  MHApplication *pApp = CurrentApp();
1376 
1377  if (pApp && pApp->m_nCharSet > 0)
1378  {
1379  return pApp->m_nCharSet;
1380  }
1381  else
1382  {
1383  return 10; // UK MHEG default.
1384  }
1385 }
1386 
1388 {
1389  MHApplication *pApp = CurrentApp();
1390 
1391  if (pApp && pApp->m_BGColour.IsSet())
1392  {
1393  colour.Copy(pApp->m_BGColour);
1394  }
1395  else
1396  {
1397  colour.SetFromString("\000\000\000\377", 4); // '=00=00=00=FF' Default - transparent
1398  }
1399 }
1400 
1402 {
1403  MHApplication *pApp = CurrentApp();
1404 
1405  if (pApp && pApp->m_TextColour.IsSet())
1406  {
1407  colour.Copy(pApp->m_TextColour);
1408  }
1409  else
1410  {
1411  colour.SetFromString("\377\377\377\000", 4); // '=FF=FF=FF=00' UK MHEG Default - white
1412  }
1413 }
1414 
1416 {
1417  MHApplication *pApp = CurrentApp();
1418 
1419  if (pApp && pApp->m_ButtonRefColour.IsSet())
1420  {
1421  colour.Copy(pApp->m_ButtonRefColour);
1422  }
1423  else
1424  {
1425  colour.SetFromString("\377\377\377\000", 4); // '=FF=FF=FF=00' ??? Not specified in UK MHEG
1426  }
1427 }
1428 
1430 {
1431  MHApplication *pApp = CurrentApp();
1432 
1433  if (pApp && pApp->m_HighlightRefColour.IsSet())
1434  {
1435  colour.Copy(pApp->m_HighlightRefColour);
1436  }
1437  else
1438  {
1439  colour.SetFromString("\377\377\377\000", 4); // '=FF=FF=FF=00' UK MHEG Default - white
1440  }
1441 }
1442 
1444 {
1445  MHApplication *pApp = CurrentApp();
1446 
1447  if (pApp && pApp->m_SliderRefColour.IsSet())
1448  {
1449  colour.Copy(pApp->m_SliderRefColour);
1450  }
1451  else
1452  {
1453  colour.SetFromString("\377\377\377\000", 4); // '=FF=FF=FF=00' UK MHEG Default - white
1454  }
1455 }
1456 
1458 {
1459  MHApplication *pApp = CurrentApp();
1460 
1461  if (pApp && pApp->m_nTextCHook > 0)
1462  {
1463  return pApp->m_nTextCHook;
1464  }
1465  else
1466  {
1467  return 10; // UK MHEG default.
1468  }
1469 }
1470 
1472 {
1473  MHApplication *pApp = CurrentApp();
1474 
1475  if (pApp && pApp->m_nStrCHook > 0)
1476  {
1477  return pApp->m_nStrCHook;
1478  }
1479  else
1480  {
1481  return 10; // UK MHEG default.
1482  }
1483 }
1484 
1486 {
1487  MHApplication *pApp = CurrentApp();
1488 
1489  if (pApp && pApp->m_nBitmapCHook > 0)
1490  {
1491  return pApp->m_nBitmapCHook;
1492  }
1493  else
1494  {
1495  return 4; // UK MHEG default - PNG bitmap
1496  }
1497 }
1498 
1500 {
1501  MHApplication *pApp = CurrentApp();
1502 
1503  if (pApp && pApp->m_FontAttrs.Size() > 0)
1504  {
1505  str.Copy(pApp->m_FontAttrs);
1506  }
1507  else
1508  {
1509  str.Copy("plain.24.24.0"); // TODO: Check this.
1510  }
1511 }
1512 
1513 // An identifier string required by the UK profile. The "manufacturer" is GNU.
1514 const char *MHEngine::MHEGEngineProviderIdString = "MHGGNU001";
1515 
1516 // Define the logging function and settings
1518 
1520 
1521 // The MHEG engine calls this when it needs to log something.
1522 void __mhlog(QString logtext)
1523 {
1524  QByteArray tmp = logtext.toLatin1();
1525  fprintf(__mhlogStream, "[freemheg] %s\n", tmp.constData());
1526 }
1527 
1528 // Called from the user of the library to set the logging.
1529 void MHSetLogging(FILE *logStream, unsigned int logLevel)
1530 {
1531  __mhlogStream = logStream;
1533 }