Index: libs/libmythtv/mhi.h
===================================================================
--- libs/libmythtv/mhi.h	(revision 18095)
+++ libs/libmythtv/mhi.h	(working copy)
@@ -182,6 +182,8 @@
 
     uint             m_lastNbiVersion;
     Q3MemArray<unsigned char> m_nbiData;
+
+    QRect            m_videoRect;
 };
 
 // Object for drawing text.
Index: libs/libmythtv/mhi.cpp
===================================================================
--- libs/libmythtv/mhi.cpp	(revision 18095)
+++ libs/libmythtv/mhi.cpp	(working copy)
@@ -37,7 +37,8 @@
       m_face_loaded(false), m_currentChannel(-1),
       m_isLive(false),      m_currentCard(0),
       m_audioTag(-1),       m_videoTag(-1),
-      m_tuningTo(-1),       m_lastNbiVersion(NBI_VERSION_UNSET)
+      m_tuningTo(-1),       m_lastNbiVersion(NBI_VERSION_UNSET),
+      m_videoRect(0, 0, StdDisplayWidth, StdDisplayHeight)
 {
     m_display.setAutoDelete(true);
     m_dsmccQueue.setAutoDelete(true);
@@ -418,6 +419,7 @@
 {
     m_displayWidth = display.width();
     m_displayHeight = display.height();
+    m_videoRect = display; // Assume full screen at this stage.
 }
 
 void MHIContext::SetInputRegister(int num)
@@ -468,7 +470,50 @@
 void MHIContext::AddToDisplay(const QImage &image, int x, int y)
 {
     MHIImageData *data = new MHIImageData;
-    data->m_image = image;
+    // It seems that OSDTypeImage::Load doesn't deal well with images
+    // located on odd pixel boundaries and the resulting display contains
+    // transparent lines.  To avoid this we create a new image if either
+    // the x or y offset would be odd and set the extra pixels to transparent.
+    QImage img = image;
+    int xboundary = x & 1;
+    int yboundary = y & 1;
+    
+    if (xboundary || yboundary)
+    {
+        int width = img.width(), height = img.height();
+        if (xboundary)
+        {
+            width++;
+            x--;
+        }
+        if (yboundary)
+        {
+            height++;
+            y--;
+        }
+        img = QImage(width, height, QImage::Format_ARGB32);
+        QRgb qTransparent = qRgba(0,0,0,0);
+        if (xboundary)
+        {
+            for (int i = 0; i < height; i++)
+                img.setPixel(0, i, qTransparent);
+        }
+
+        if (yboundary)
+        {
+            for (int j = 0; j < width; j++)
+                img.setPixel(j, 0, qTransparent);
+        }
+
+        for (int i = 0; i < height-yboundary; i++)
+        {
+            for (int j = 0; j < width-xboundary; j++)
+            {
+                img.setPixel(j+xboundary, i+yboundary, image.pixel(j,i));
+            }
+        }
+    }
+    data->m_image = img;
     data->m_x = x;
     data->m_y = y;
     QMutexLocker locker(&m_display_lock);
@@ -486,7 +531,17 @@
 {
     // tell the video player to resize the video stream
     if (m_parent->GetNVP())
-        m_parent->GetNVP()->SetVideoResize(videoRect);
+    {
+        QRect vidRect(videoRect.x() * m_displayWidth/StdDisplayWidth,
+                      videoRect.y() * m_displayHeight/StdDisplayHeight,
+                      videoRect.width() * m_displayWidth/StdDisplayWidth,
+                      videoRect.height() * m_displayHeight/StdDisplayHeight);
+        if (m_videoRect != vidRect)
+        {
+            m_parent->GetNVP()->SetVideoResize(vidRect);
+            m_videoRect = vidRect;
+        }
+    }
 
     QMutexLocker locker(&m_display_lock);
     QRect displayRect(dispRect.x() * m_displayWidth/StdDisplayWidth,
@@ -729,15 +784,10 @@
     QRgb qColour = qRgba(colour.red(), colour.green(),
                          colour.blue(), colour.alpha());
 
-    // This is a bit of a mess: we should be able to create a rectangle object.
-    // Scale the image to the current display size
     int scaledWidth = width * GetWidth() / MHIContext::StdDisplayWidth;
     int scaledHeight = height * GetHeight() / MHIContext::StdDisplayHeight;
-    QImage qImage(scaledWidth, scaledHeight, 32);
-    qImage.setAlphaBuffer(true);
+    QImage qImage(scaledWidth, scaledHeight, QImage::Format_ARGB32);
 
-    // As far as I can tell this is the only way to draw with an
-    // intermediate transparency.
     for (int i = 0; i < scaledHeight; i++)
     {
         for (int j = 0; j < scaledWidth; j++)
@@ -921,9 +971,7 @@
 // different layers.  The background is drawn separately as a rectangle.
 void MHIText::Clear(void)
 {
-    m_image = QImage(m_width, m_height, 32);
-    // 
-    m_image.setAlphaBuffer(true);
+    m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
     // QImage::fill doesn't set the alpha buffer.
     for (int i = 0; i < m_height; i++)
     {
@@ -1062,7 +1110,7 @@
         m_image = QImage();
         return;
     }
-    m_image = QImage(m_width, m_height, 32);
+    m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
     // Fill the image with "transparent colour".
     DrawRect(0, 0, m_width, m_height, MHRgba(0, 0, 0, 0));
 }
@@ -1274,6 +1322,8 @@
 // The UK profile says that MHEG should not contain concave or
 // self-crossing polygons but we can get the former at least as
 // a result of rounding when drawing ellipses.
+typedef struct { int yBottom, yTop, xBottom; float slope; } lineSeg;
+
 void MHIDLA::DrawPoly(bool isFilled, const Q3PointArray &points)
 {
     int nPoints = points.size();
@@ -1282,116 +1332,73 @@
 
     if (isFilled)
     {
-        // Polygon filling is done by sketching the outline of
-        // the polygon in a separate bitmap and then raster scanning
-        // across this to generate the fill.  There are some special
-        // cases that have to be considered when doing this.  Maximum
-        // and minimum points have to be removed otherwise they will
-        // turn the scan on but not off again.  Horizontal lines are
-        // suppressed and their ends handled specially.
-        QRect bounds = points.boundingRect();
-        int width = bounds.width()+1, height = bounds.height()+1;
-        QBitArray boundsMap(width*height);
-        boundsMap.fill(0);
-        // Draw the boundaries in the bounds map.  This is
-        // the Bresenham algorithm if the absolute gradient is
-        // greater than 1 but puts only the centre of each line
-        // (so there is only one point for each y value) if less.
-        QPoint last = points[nPoints-1]; // Last point
-        for (int i = 0; i < nPoints; i++)
+        Q3MemArray <lineSeg> lineArray(nPoints);
+        int nLines = 0;
+        // Initialise the line segment array.  Include all lines
+        // apart from horizontal.  Close the polygon by starting
+        // with the last point in the array.
+        int lastX = points[nPoints-1].x(); // Last point
+        int lastY = points[nPoints-1].y();
+        int yMin = lastY, yMax = lastY;
+        for (int k = 0; k < nPoints; k++)
         {
-            QPoint thisPoint = points[i];
-            int x1 = last.x() - bounds.x();
-            int y1 = last.y() - bounds.y();
-            int x2 = thisPoint.x() - bounds.x();
-            int y2 = thisPoint.y() - bounds.y();
-            int x, xEnd, y, yEnd;
-            if (y2 > y1)
+            int thisX = points[k].x();
+            int thisY = points[k].y();
+            if (lastY != thisY)
             {
-                x = x1;
-                y = y1;
-                xEnd = x2;
-                yEnd = y2;
-            }
-            else
-            {
-                x = x2;
-                y = y2;
-                xEnd = x1;
-                yEnd = y1;
-            }
-            int dx = abs(xEnd-x), dy = yEnd-y;
-            int xStep = xEnd >= x ? 1 : -1;
-            if (abs(y2-y1) > abs(x2-x1))
-            {
-                int error = dy/2;
-                y++;
-                for (; y < yEnd; y++) // Exclude endpoints
+                if (lastY > thisY)
                 {
-                    boundsMap.toggleBit(x+y*width);
-                    error += dx;
-                    if (error*2 > dy)
-                    {
-                        error -= dy;
-                        x += xStep;
-                    }
+                    lineArray[nLines].yBottom = thisY;
+                    lineArray[nLines].yTop = lastY;
+                    lineArray[nLines].xBottom = thisX;
                 }
-            }
-            else
-            {
-                int error = 0;
-                y++;
-                for (; y < yEnd; y++)
+                else
                 {
-                    boundsMap.toggleBit(x+y*width);
-                    error += dx;
-                    while (error > dy)
-                    {
-                        x += xStep;
-                        error -= dy;
-                    }
+                    lineArray[nLines].yBottom = lastY;
+                    lineArray[nLines].yTop = thisY;
+                    lineArray[nLines].xBottom = lastX;
                 }
+                lineArray[nLines++].slope =
+                    (float)(thisX-lastX) / (float)(thisY-lastY);
             }
-            QPoint nextPoint = points[(i+1) % nPoints];
-            int nextY = nextPoint.y() - bounds.y();
-            int turn = (y2 - y1) * (nextY - y2);
-            if (turn > 0) // Not a max or min
-                boundsMap.toggleBit(x2+y2*width);
-            else if (turn == 0) // Previous or next line is horizontal
-            {
-                // We only draw a point at the beginning or end of a horizontal
-                // line if it turns clockwise.  This means that the fill
-                // will be different depending on the direction the polygon was
-                // drawn but that will be tidied up when we draw the lines round.
-                if (y1 == y2)
-                {
-                    if ((x2-x1) * (nextY - y2) > 0)
-                       boundsMap.toggleBit(x2+y2*width);
-                }
-                else if ((nextPoint.x() - bounds.x() - x2) * (y2 - y1) < 0)
-                    // Next line is horizontal -  draw point if turn is clockwise.
-                    boundsMap.toggleBit(x2+y2*width);
-            }
-            last = thisPoint;
+            if (thisY < yMin)
+                yMin = thisY;
+            if (thisY > yMax)
+                yMax = thisY;
+            lastX = thisX;
+            lastY = thisY;
         }
+        
+        // Find the intersections of each line in the line segment array
+        // with the scan line.  Because UK MHEG says that figures should be
+        // convex we only need to consider two intersections.
         QRgb fillColour = qRgba(m_fillColour.red(), m_fillColour.green(),
                                 m_fillColour.blue(), m_fillColour.alpha());
-        // Now scan the bounds map and use this to fill the polygon.
-        for (int j = 0; j < bounds.height(); j++)
+        for (int y = yMin; y < yMax; y++)
         {
-            bool penDown = false;
-            for (int k = 0; k < bounds.width(); k++)
+            int crossings = 0, xMin = 0, xMax = 0;
+            for (int l = 0; l < nLines; l++)
             {
-                if (boundsMap.testBit(k+j*width))
-                    penDown = ! penDown;
-                else if (penDown && k+bounds.x() >= 0 && j+bounds.y() >= 0 &&
-                         k+bounds.x() < m_width && j+bounds.y() < m_height)
-                    m_image.setPixel(k+bounds.x(), j+bounds.y(), fillColour);
+                if (y >= lineArray[l].yBottom && y < lineArray[l].yTop)
+                {
+                    int x = (int)round((float)(y - lineArray[l].yBottom) * 
+                        lineArray[l].slope) + lineArray[l].xBottom;
+                    if (crossings == 0 || x < xMin)
+                        xMin = x;
+                    if (crossings == 0 || x > xMax)
+                        xMax = x;
+                    crossings++;
+                }
             }
+            if (crossings == 2)
+            {
+                for (int x = xMin; x <= xMax; x++)
+                    m_image.setPixel(x, y, fillColour);
+            }
         }
 
         // Draw the boundary
-        last = points[nPoints-1]; // Last point
+        QPoint last = points[nPoints-1]; // Last point
         for (int i = 0; i < nPoints; i++)
         {
             DrawLine(points[i].x(), points[i].y(), last.x(), last.y());
@@ -1502,7 +1509,7 @@
     {
         int nContentWidth = c->width;
         int nContentHeight = c->height;
-        m_image = QImage(nContentWidth, nContentHeight, 32);
+        m_image = QImage(nContentWidth, nContentHeight, QImage::Format_ARGB32);
         m_opaque = true; // MPEG images are always opaque.
 
         AVPicture retbuf;
@@ -1559,3 +1566,5 @@
     m_image = m_image.scaled(newWidth, newHeight,
             Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 }
+
+
Index: libs/libmythfreemheg/Actions.cpp
===================================================================
--- libs/libmythfreemheg/Actions.cpp	(revision 18095)
+++ libs/libmythfreemheg/Actions.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* Actions.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -93,8 +93,8 @@
         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 MHUnimplementedAction(pElemAction->GetTagNo()); break;// ?
-        case C_GET_INTERACTION_STATUS: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); 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
@@ -104,11 +104,11 @@
         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 MHUnimplementedAction(pElemAction->GetTagNo()); break;// Slider
+        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 MHUnimplementedAction(pElemAction->GetTagNo()); break;// Slider
+        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;
@@ -145,24 +145,24 @@
         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 MHUnimplementedAction(pElemAction->GetTagNo()); break; // ?
-        case C_SET_INTERACTION_STATUS: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // ?
+        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 MHUnimplementedAction(pElemAction->GetTagNo()); break; // Slider
+        case C_SET_PORTION: pAction = new MHSetPortion; break;
         case C_SET_POSITION: pAction = new MHSetPosition; break;
-        case C_SET_SLIDER_VALUE: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); break; // Slider
+        case C_SET_SLIDER_VALUE: pAction = new MHSetSliderValue; break;
         case C_SET_SPEED: pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); 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 MHUnimplementedAction(pElemAction->GetTagNo()); break; // Slider
+        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;
@@ -184,7 +184,7 @@
         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 MHUnimplementedAction(pElemAction->GetTagNo()); break; // ?
+        case C_SET_SLIDER_PARAMETERS: pAction = new MHSetSliderParameters; break;
 
         default:
             MHLOG(MHLogWarning, QString("Unknown action %1").arg(pElemAction->GetTagNo()));
Index: libs/libmythfreemheg/Engine.cpp
===================================================================
--- libs/libmythfreemheg/Engine.cpp	(revision 18095)
+++ libs/libmythfreemheg/Engine.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* Engine.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -31,6 +31,7 @@
 #include "ASN1Codes.h"
 #include "Logging.h"
 #include "freemheg.h"
+#include "Visible.h"  // For MHInteractible
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -46,6 +47,7 @@
 {
     m_fInTransition = false;
     m_fBooting = true;
+    m_Interacting = 0;
 }
 
 MHEngine::~MHEngine()
@@ -323,6 +325,7 @@
         pApp->m_pCurrentScene = NULL;
     }
 
+    m_Interacting = 0;
 
     // Switch to the new scene.
     CurrentApp()->m_pCurrentScene = (MHScene*) pProgram;
@@ -620,7 +623,29 @@
 {
     MHScene *pScene = CurrentScene();
     if (! pScene) return;
-    EventTriggered(pScene, EventUserInput, nCode);
+    // Various keys generate engine events as well as user events.
+    // These are generated before the user events and even if there
+    // is an interactible.
+    switch (nCode)
+    {
+    case 104:
+    case 105: // Text key
+        EventTriggered(pScene, EventEngineEvent, 4);
+        break;
+    case 16: // Text Exit/Cancel key
+    case 100: // Red
+    case 101: // Green
+    case 102: // Yellow
+    case 103: // Blue
+        EventTriggered(pScene, EventEngineEvent, nCode);
+        break;
+    }
+
+    // If we are interacting with an interactible send the key
+    // there otherwise generate a user event.
+    if (m_Interacting)
+        m_Interacting->KeyEvent(this, nCode);
+    else EventTriggered(pScene, EventUserInput, nCode);
 }
 
 // Called by an ingredient wanting external content.
Index: libs/libmythfreemheg/ParseText.cpp
===================================================================
--- libs/libmythfreemheg/ParseText.cpp	(revision 18095)
+++ libs/libmythfreemheg/ParseText.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* ParseText.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -536,6 +536,10 @@
                 if (m_nInt > 0) return;
                 m_nInt = MHText::GetStartCorner(buff);
                 if (m_nInt > 0) return;
+                m_nInt = MHSlider::GetOrientation(buff);
+                if (m_nInt > 0) return;
+                m_nInt = MHSlider::GetStyle(buff);
+                if (m_nInt > 0) return;
 
                 // Check the colour table.  If it's there generate a string containing the colour info.
                 for (int i = 0; i < (int)(sizeof(colourTable)/sizeof(colourTable[0])); i++) {
Index: libs/libmythfreemheg/Text.cpp
===================================================================
--- libs/libmythfreemheg/Text.cpp	(revision 18095)
+++ libs/libmythfreemheg/Text.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* Text.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -465,6 +465,11 @@
                     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.
@@ -536,7 +541,7 @@
 }
 
 
-MHHyperText::MHHyperText()
+MHHyperText::MHHyperText(): MHInteractible(this)
 {
 
 }
Index: libs/libmythfreemheg/Root.h
===================================================================
--- libs/libmythfreemheg/Root.h	(revision 18095)
+++ libs/libmythfreemheg/Root.h	(working copy)
@@ -176,6 +176,22 @@
     virtual void SetVideoDecodeOffset(int /*newXOffset*/, int /*newYOffset*/, MHEngine *) { InvalidAction("SetVideoDecodeOffset"); }
     virtual void GetVideoDecodeOffset(MHRoot * /*pXOffset*/, MHRoot */*pYOffset*/, MHEngine *) { InvalidAction("GetVideoDecodeOffset"); }
 
+    // Actions on Interactibles.
+    virtual void SetInteractionStatus(bool /*newStatus*/, MHEngine *) { InvalidAction("SetInteractionStatus"); }
+    virtual bool GetInteractionStatus(void) { InvalidAction("GetInteractionStatus"); return false; }
+    virtual void SetHighlightStatus(bool /*newStatus*/, MHEngine *engine) { InvalidAction("SetHighlightStatus"); }
+    virtual bool GetHighlightStatus(void) { InvalidAction("GetHighlightStatus"); return false; }
+
+    // Actions on Sliders.
+    virtual void Step(int /*nbSteps*/, MHEngine */*engine*/) { InvalidAction("Step"); }
+    virtual void SetSliderValue(int /*nbSteps*/, MHEngine */*engine*/) { InvalidAction("SetSliderValue"); }
+    virtual int GetSliderValue(void) { InvalidAction("GetSliderValue"); return 0; }
+    virtual void SetPortion(int /*newPortion*/, MHEngine */*engine*/) { InvalidAction("SetPortion"); }
+    virtual int GetPortion(void) { InvalidAction("GetPortion"); return 0; }
+    // Additional action defined in UK MHEG.
+    virtual void SetSliderParameters(int /*newMin*/, int /*newMax*/, int /*newStep*/, MHEngine */*engine*/)
+         { InvalidAction("SetSliderParameters"); }
+
 protected:
 
     void InvalidAction(const char *actionName);
Index: libs/libmythfreemheg/BaseActions.h
===================================================================
--- libs/libmythfreemheg/BaseActions.h	(revision 18095)
+++ libs/libmythfreemheg/BaseActions.h	(working copy)
@@ -1,6 +1,6 @@
 /* BaseActions.h
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -73,6 +73,19 @@
     MHGenericInteger m_Argument1, m_Argument2;
 };
 
+// Base class for actions with three integers.  Used for SetSliderParameters
+class MHActionInt3: public MHElemAction
+{
+public:
+    MHActionInt3(const char *name): MHElemAction(name) {}
+    virtual void Initialise(MHParseNode *p, MHEngine *engine);
+    virtual void PrintArgs(FILE *fd, int nTabs) const;
+    virtual void Perform(MHEngine *engine);
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg1, int nArg2, int nArg3) = 0;
+protected:
+    MHGenericInteger m_Argument1, m_Argument2, m_Argument3;
+};
+
 // Base class for actions with four integers.  Used in the DynamicLineArt class
 class MHActionInt4: public MHElemAction
 {
@@ -139,4 +152,17 @@
 };
 
 
+// Base class for actions with a single boolean argument.
+class MHActionBool: public MHElemAction
+{
+public:
+    MHActionBool(const char *name): MHElemAction(name) {}
+    virtual void Initialise(MHParseNode *p, MHEngine *engine);
+    virtual void PrintArgs(FILE *fd, int) const { m_Argument.PrintMe(fd, 0); }
+    virtual void Perform(MHEngine *engine);
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, bool fArg) = 0;
+protected:
+    MHGenericBoolean m_Argument;
+};
+
 #endif
Index: libs/libmythfreemheg/Visible.h
===================================================================
--- libs/libmythfreemheg/Visible.h	(revision 18095)
+++ libs/libmythfreemheg/Visible.h	(working copy)
@@ -1,6 +1,6 @@
 /* Visible.h
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -124,13 +124,41 @@
     virtual MHIngredient *Clone(MHEngine *) { return new MHRectangle(*this); } // Create a clone of this ingredient.
 };
 
+// The Interactible class is described as a "mix-in" class.  It is used
+// in various classes which complicates inheritance.
 class MHInteractible
 {
 public:
-    MHInteractible();
+    MHInteractible(MHVisible *parent);
     virtual ~MHInteractible();
-    virtual void Initialise(MHParseNode *p, MHEngine *engine);
-    virtual void PrintMe(FILE *fd, int nTabs) const;
+    void Initialise(MHParseNode *p, MHEngine *engine);
+    void PrintMe(FILE *fd, int nTabs) const;
+
+    virtual void Interaction(MHEngine *engine);
+
+    // This is called whenever a key is pressed while this
+    // interactible is set to interactive.
+    virtual void KeyEvent(MHEngine */*engine*/, int /*nCode*/) {}
+    virtual void InteractionCompleted(MHEngine */*engine*/) {}
+
+    void InteractSetInteractionStatus(bool newStatus, MHEngine *engine);
+    bool InteractGetInteractionStatus(void) { return m_fInteractionStatus; }
+    void InteractSetHighlightStatus(bool newStatus, MHEngine *engine);
+    bool InteractGetHighlightStatus(void) { return m_fHighlightStatus; }
+    // InteractDeactivation should be applied in every Deactivation action
+    // of derived classes.
+    void InteractDeactivation(void) { m_fInteractionStatus = false; }
+
+protected:
+    // Exchanged attributes
+    bool     m_fEngineResp;
+    MHColour m_highlightRefColour;
+    // Internal attributes
+    bool     m_fHighlightStatus;
+    bool     m_fInteractionStatus;
+
+private:
+    MHVisible *m_parent;
 };
 
 class MHSlider : public MHVisible, public MHInteractible
@@ -141,7 +169,54 @@
     virtual const char *ClassName() { return "Slider"; }
     virtual void Initialise(MHParseNode *p, MHEngine *engine);
     virtual void PrintMe(FILE *fd, int nTabs) const;
-    virtual void Display(MHEngine *) {} // Not (yet?) supported
+    virtual void Display(MHEngine *);
+    virtual void Preparation(MHEngine *engine);
+
+    virtual void Interaction(MHEngine *engine);
+    virtual void InteractionCompleted(MHEngine *engine);
+    virtual void KeyEvent(MHEngine *engine, int nCode);
+
+    // Implement the actions in the main inheritance line.
+    virtual void SetInteractionStatus(bool newStatus, MHEngine *engine)
+    { InteractSetInteractionStatus(newStatus, engine); }
+    virtual bool GetInteractionStatus(void) { return InteractGetInteractionStatus(); }
+    virtual void SetHighlightStatus(bool newStatus, MHEngine *engine)
+    { InteractSetHighlightStatus(newStatus, engine); }
+    virtual bool GetHighlightStatus(void) { return InteractGetHighlightStatus(); }
+    virtual void Deactivation(MHEngine *engine) { InteractDeactivation(); }
+
+    // Actions
+    virtual void Step(int nbSteps, MHEngine *engine);
+    virtual void SetSliderValue(int newValue, MHEngine *engine);
+    virtual int GetSliderValue(void) { return slider_value; }
+    virtual void SetPortion(int newPortion, MHEngine *engine);
+    virtual int GetPortion(void) { return portion; }
+    // Additional action defined in UK MHEG.
+    virtual void SetSliderParameters(int newMin, int newMax, int newStep, MHEngine *engine);
+
+    // Enumerated type lookup functions for the text parser.
+    static int GetOrientation(const char *str);
+    static int GetStyle(const char *str);
+protected:
+    void Increment(MHEngine *engine);
+    void Decrement(MHEngine *engine);
+
+    // Exchanged attributes
+    // Orientation and direction of increasing value.
+    enum SliderOrientation { SliderLeft = 1, SliderRight, SliderUp, SliderDown }
+        m_orientation;
+    int initial_value, initial_portion;
+    int orig_max_value, orig_min_value, orig_step_size;
+    // Style of slider.  Normal represents a mark on a scale,
+    // Thermometer a range from the start up to the mark and Proportional
+    // a range from the slider to the portion.
+    enum SliderStyle { SliderNormal = 1, SliderThermo, SliderProp }
+        m_style;
+    MHColour m_sliderRefColour;
+    // Internal attributes
+    // In UK MHEG min_value, max_value and step_size can be changed.
+    int max_value, min_value, step_size;
+    int slider_value, portion;
 };
 
 class MHEntryField : public MHVisible, public MHInteractible
@@ -153,6 +228,15 @@
     virtual void Initialise(MHParseNode *p, MHEngine *engine);
     virtual void PrintMe(FILE *fd, int nTabs) const;
     virtual void Display(MHEngine *) {} // Not (yet?) supported
+
+    // Implement the actions in the main inheritance line.
+    virtual void SetInteractionStatus(bool newStatus, MHEngine *engine)
+    { InteractSetInteractionStatus(newStatus, engine); }
+    virtual bool GetInteractionStatus(void) { return InteractGetInteractionStatus(); }
+    virtual void SetHighlightStatus(bool newStatus, MHEngine *engine)
+    { InteractSetHighlightStatus(newStatus, engine); }
+    virtual bool GetHighlightStatus(void) { return InteractGetHighlightStatus(); }
+    virtual void Deactivation(MHEngine *engine) { InteractDeactivation(); }
 };
 
 // Button - not needed for UK MHEG.
@@ -311,4 +395,81 @@
     virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg) { pTarget->SetLineStyle(nArg, engine); };
 };
 
+class MHSetInteractionStatus: public MHActionBool
+{
+public:
+    MHSetInteractionStatus(): MHActionBool("SetInteractionStatus") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, bool newStatus)
+    { Target(engine)->SetInteractionStatus(newStatus, engine); }
+};
+
+class MHGetInteractionStatus: public MHActionObjectRef
+{
+public:
+    MHGetInteractionStatus(): MHActionObjectRef(":GetInteractionStatus")  {}
+    virtual void CallAction(MHEngine *, MHRoot *pTarget, MHRoot *pResult)
+        { pResult->SetVariableValue(pTarget->GetInteractionStatus());}
+};
+
+class MHSetHighlightStatus: public MHActionBool
+{
+public:
+    MHSetHighlightStatus(): MHActionBool("SetHighlightStatus") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, bool newStatus)
+    { Target(engine)->SetHighlightStatus(newStatus, engine); }
+};
+
+class MHGetHighlightStatus: public MHActionObjectRef
+{
+public:
+    MHGetHighlightStatus(): MHActionObjectRef(":GetHighlightStatus")  {}
+    virtual void CallAction(MHEngine *, MHRoot *pTarget, MHRoot *pResult)
+        { pResult->SetVariableValue(pTarget->GetHighlightStatus());}
+};
+
+class MHStep: public MHActionInt
+{
+public:
+    MHStep(): MHActionInt(":Step") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg) { pTarget->Step(nArg, engine); };
+};
+
+class MHSetSliderValue: public MHActionInt
+{
+public:
+    MHSetSliderValue(): MHActionInt(":SetSliderValue") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg) { pTarget->SetSliderValue(nArg, engine); };
+};
+
+class MHGetSliderValue: public MHActionObjectRef
+{
+public:
+    MHGetSliderValue(): MHActionObjectRef(":GetSliderValue")  {}
+    virtual void CallAction(MHEngine *, MHRoot *pTarget, MHRoot *pResult)
+        { pResult->SetVariableValue(pTarget->GetSliderValue());}
+};
+
+class MHSetPortion: public MHActionInt
+{
+public:
+    MHSetPortion(): MHActionInt(":SetPortion") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg) { pTarget->SetPortion(nArg, engine); };
+};
+
+class MHGetPortion: public MHActionObjectRef
+{
+public:
+    MHGetPortion(): MHActionObjectRef(":GetPortion")  {}
+    virtual void CallAction(MHEngine *, MHRoot *pTarget, MHRoot *pResult)
+        { pResult->SetVariableValue(pTarget->GetPortion());}
+};
+
+class MHSetSliderParameters: public MHActionInt3
+{
+public:
+    MHSetSliderParameters(): MHActionInt3(":SetSliderParameters") {}
+    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int newMin, int newMax, int newStep)
+        { pTarget->SetSliderParameters(newMin, newMax, newStep, engine); };
+};
+
 #endif
Index: libs/libmythfreemheg/Engine.h
===================================================================
--- libs/libmythfreemheg/Engine.h	(revision 18095)
+++ libs/libmythfreemheg/Engine.h	(working copy)
@@ -1,6 +1,6 @@
 /* Engine.h
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -64,6 +64,8 @@
     MHIngredient *m_pRequester; 
 };
 
+class MHInteractible;
+
 class MHEngine: public MHEG {
 public:
     MHEngine(MHContext *context);
@@ -149,6 +151,10 @@
 
     static const char *MHEGEngineProviderIdString;
 
+    // Interaction: Set if an Interactible has the focus and is receiving key presses.
+    MHInteractible *GetInteraction(void) { return m_Interacting; }
+    void SetInteraction(MHInteractible *p) { m_Interacting = p; }
+
 protected:
     void CheckLinks(const MHObjectRef &sourceRef, enum EventType ev, const MHUnion &un);
     MHGroup *ParseProgram(QByteArray &text);
@@ -193,6 +199,8 @@
 
     MHContext       *m_Context; // Pointer to the context providing drawing and other operations
     bool            m_fBooting;
+
+    MHInteractible  *m_Interacting; // Set to current interactive object if any.
 };
 
 #endif
Index: libs/libmythfreemheg/Bitmap.cpp
===================================================================
--- libs/libmythfreemheg/Bitmap.cpp	(revision 18095)
+++ libs/libmythfreemheg/Bitmap.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* Bitmap.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -113,7 +113,10 @@
     if (nCHook == 4) { // PNG.
         m_pContent->CreateFromPNG(data, length);
     }
-    else if (nCHook == 2) { // MPEG I-frame.
+    // CHook 5 seems to be used by the BBC on Freesat for an MPEG I-frame for the
+    // background but enabling it here results in it overlaying the video.
+    // Presumably it is not simply the same as CHook 2.
+    else if (nCHook == 2 /* ||nCHook == 5 */) { // MPEG I-frame.
         m_pContent->CreateFromMPEG(data, length);
     }
 
Index: libs/libmythfreemheg/freemheg.h
===================================================================
--- libs/libmythfreemheg/freemheg.h	(revision 18095)
+++ libs/libmythfreemheg/freemheg.h	(working copy)
@@ -69,7 +69,7 @@
 {
 public:
     MHRgba(int red, int green, int blue, int alpha):
-      m_red(Qt::red), m_green(Qt::green), m_blue(Qt::blue), m_alpha(alpha) {};
+      m_red(red), m_green(green), m_blue(blue), m_alpha(alpha) {};
     MHRgba(): m_red(0), m_green(0), m_blue(0), m_alpha(0) {};
     int red() const { return m_red; }
     int green() const { return m_green; }
Index: libs/libmythfreemheg/Text.h
===================================================================
--- libs/libmythfreemheg/Text.h	(revision 18095)
+++ libs/libmythfreemheg/Text.h	(working copy)
@@ -90,7 +90,7 @@
     void CreateContent(const unsigned char *p, int s, MHEngine *engine);
 };
 
-class MHHyperText : public MHText, public  MHInteractible
+class MHHyperText : public MHText, public MHInteractible
 {
 public:
     MHHyperText();
@@ -98,6 +98,15 @@
     virtual ~MHHyperText();
     virtual void Initialise(MHParseNode *p, MHEngine *engine);
     virtual void PrintMe(FILE *fd, int nTabs) const;
+
+    // Implement the actions in the main inheritance line.
+    virtual void SetInteractionStatus(bool newStatus, MHEngine *engine)
+    { InteractSetInteractionStatus(newStatus, engine); }
+    virtual bool GetInteractionStatus(void) { return InteractGetInteractionStatus(); }
+    virtual void SetHighlightStatus(bool newStatus, MHEngine *engine)
+    { InteractSetHighlightStatus(newStatus, engine); }
+    virtual bool GetHighlightStatus(void) { return InteractGetHighlightStatus(); }
+    virtual void Deactivation(MHEngine *engine) { InteractDeactivation(); }
 };
 
 // Get Text Data - get the data out of a text object.
Index: libs/libmythfreemheg/Programs.cpp
===================================================================
--- libs/libmythfreemheg/Programs.cpp	(revision 18095)
+++ libs/libmythfreemheg/Programs.cpp	(working copy)
@@ -492,7 +492,31 @@
         }
 
         else if (m_Name.Equal("DBG")) { // Debug - optional
-            MHERROR("Debug ResidentProgram is not implemented");
+            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 {
Index: libs/libmythfreemheg/BaseActions.cpp
===================================================================
--- libs/libmythfreemheg/BaseActions.cpp	(revision 18095)
+++ libs/libmythfreemheg/BaseActions.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* BaseActions.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
@@ -87,6 +87,26 @@
     m_ResultVar2.Initialise(p->GetArgN(2), engine);
 }
 
+void MHActionInt3::Initialise(MHParseNode *p, MHEngine *engine)
+{
+    MHElemAction::Initialise(p, engine);
+    m_Argument1.Initialise(p->GetArgN(1), engine);
+    m_Argument2.Initialise(p->GetArgN(2), engine);
+    m_Argument3.Initialise(p->GetArgN(3), engine);
+}
+
+void MHActionInt3::PrintArgs(FILE *fd, int /*nTabs*/) const
+{
+    m_Argument1.PrintMe(fd, 0);
+    m_Argument2.PrintMe(fd, 0);
+    m_Argument3.PrintMe(fd, 0);
+}
+
+void MHActionInt3::Perform(MHEngine *engine)
+{
+    CallAction(engine, Target(engine), m_Argument1.GetValue(engine), m_Argument2.GetValue(engine), m_Argument3.GetValue(engine));
+}
+
 void MHActionInt4::Initialise(MHParseNode *p, MHEngine *engine)
 {
     MHElemAction::Initialise(p, engine);
@@ -160,3 +180,15 @@
 {
     CallAction(engine, Target(engine), engine->FindObject(m_ResultVar1), engine->FindObject(m_ResultVar2));
 }
+
+void MHActionBool::Initialise(MHParseNode *p, MHEngine *engine)
+{
+    MHElemAction::Initialise(p, engine);
+    m_Argument.Initialise(p->GetArgN(1), engine);
+}
+
+
+void MHActionBool::Perform(MHEngine *engine)
+{
+    CallAction(engine, Target(engine), m_Argument.GetValue(engine));
+}
Index: libs/libmythfreemheg/Link.cpp
===================================================================
--- libs/libmythfreemheg/Link.cpp	(revision 18095)
+++ libs/libmythfreemheg/Link.cpp	(working copy)
@@ -1,6 +1,6 @@
 /* Link.cpp
 
-   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk
+   Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
Index: libs/libmythfreemheg/TokenGroup.cpp
===================================================================
--- libs/libmythfreemheg/TokenGroup.cpp	(revision 18095)
+++ libs/libmythfreemheg/TokenGroup.cpp	(working copy)
@@ -54,7 +54,8 @@
         for (int i = 0; i < m_ActionSlots.Size(); i++) {
             PrintTabs(fd, nTabs+2); fprintf(fd, "(\n");
             MHActionSequence *pActions = m_ActionSlots.GetAt(i);
-            if (pActions->Size() == 0) fprintf(fd, "NULL\n");
+            if (pActions->Size() == 0) 
+                { PrintTabs(fd, nTabs+2); fprintf(fd, "NULL\n"); }
             else pActions->PrintMe(fd, nTabs+2);
             PrintTabs(fd, nTabs+2); fprintf(fd, ")\n");
         }
Index: libs/libmythfreemheg/Visible.cpp
===================================================================
--- libs/libmythfreemheg/Visible.cpp	(revision 18095)
+++ libs/libmythfreemheg/Visible.cpp	(working copy)
@@ -355,9 +355,11 @@
 }
 
 
-MHInteractible::MHInteractible()
+MHInteractible::MHInteractible(MHVisible *parent): m_parent(parent)
 {
-
+    m_fEngineResp = true;
+    m_fHighlightStatus = false;
+    m_fInteractionStatus = false;
 }
 
 MHInteractible::~MHInteractible()
@@ -365,45 +367,393 @@
 
 }
 
-void MHInteractible::Initialise(MHParseNode */*p*/, MHEngine */*engine*/)
+void MHInteractible::Initialise(MHParseNode *p, MHEngine *engine)
 {
+    // Engine Resp - optional
+    MHParseNode *pEngineResp = p->GetNamedArg(C_ENGINE_RESP);
+    if (pEngineResp) m_fEngineResp = pEngineResp->GetArgN(0)->GetBoolValue();
+    // Highlight colour.
+    MHParseNode *phlCol = p->GetNamedArg(C_HIGHLIGHT_REF_COLOUR);
+    if (phlCol) m_highlightRefColour.Initialise(phlCol->GetArgN(0), engine);
+    else engine->GetDefaultHighlightRefColour(m_highlightRefColour);
+    m_fHighlightStatus = false;
+    m_fInteractionStatus = false;
 }
 
-void MHInteractible::PrintMe(FILE */*fd*/, int /*nTabs*/) const
+void MHInteractible::PrintMe(FILE *fd, int nTabs) const
 {
+    if (! m_fEngineResp)  { PrintTabs(fd, nTabs); fprintf(fd, ":EngineResp false\n"); }
+
+    if (m_highlightRefColour.IsSet()) {
+        PrintTabs(fd, nTabs);
+        fprintf(fd, ":HighlightRefColour ");
+        m_highlightRefColour.PrintMe(fd, nTabs+1);
+        fprintf(fd, "\n");
+    }
 }
 
-MHSlider::MHSlider()
+void MHInteractible::Interaction(MHEngine *engine)
 {
+    m_fInteractionStatus = true;
+    engine->SetInteraction(this);
+    // The MHEG standard says: "generate visual feedback" here
+    // but it appears that any visual feedback is controlled only
+    // by the highlight status combined with engine-resp.
+}
 
+void MHInteractible::InteractSetInteractionStatus(bool newStatus, MHEngine *engine)
+{
+    if (newStatus) { // Turning interaction on.
+        if (engine->GetInteraction() == 0) // No current interactible
+            Interaction(engine); // virtual function
+    }
+    else { // Turning interaction off.
+        if (m_fInteractionStatus) {
+            m_fInteractionStatus = false;
+            engine->SetInteraction(0);
+            InteractionCompleted(engine); // Interaction is interrupted.
+            engine->EventTriggered(m_parent, EventInteractionCompleted);
+        }
+    }
 }
 
-MHSlider::~MHSlider()
+void MHInteractible::InteractSetHighlightStatus(bool newStatus, MHEngine *engine)
 {
+    if (newStatus == m_fHighlightStatus) return;
+    m_fHighlightStatus = newStatus;
+    // If active redraw to show change of status.
+    if (m_parent->GetRunningStatus() && m_fEngineResp)
+        engine->Redraw(m_parent->GetVisibleArea());
+    // Generate the event for the change of highlight status.
+    engine->EventTriggered(m_parent, m_fHighlightStatus ? EventHighlightOn: EventHighlightOff);
+}
 
+MHSlider::MHSlider(): MHInteractible(this)
+{
+    m_orientation = SliderLeft;
+    orig_max_value = -1;
+    orig_min_value = initial_value = orig_step_size = 1;
+    initial_portion = 0;
+    m_style = SliderNormal;
 }
 
+MHSlider::~MHSlider()
+{
+}
+
 void MHSlider::Initialise(MHParseNode *p, MHEngine *engine)
 {
     MHVisible::Initialise(p, engine);
     MHInteractible::Initialise(p, engine);
-    //
+    // 
+    MHParseNode *pOrientation = p->GetNamedArg(C_ORIENTATION);
+    if (pOrientation)
+        m_orientation = (enum SliderOrientation)pOrientation->GetArgN(0)->GetEnumValue();
+    // This is not optional.
+
+    MHParseNode *pMin = p->GetNamedArg(C_MIN_VALUE);
+    if (pMin) orig_min_value = pMin->GetArgN(0)->GetIntValue();
+    else orig_min_value = 1;
+
+    MHParseNode *pMax = p->GetNamedArg(C_MAX_VALUE);
+    if (pMax) orig_max_value = pMax->GetArgN(0)->GetIntValue();
+    else orig_max_value = orig_min_value-1; // Unset
+
+    MHParseNode *pInit = p->GetNamedArg(C_INITIAL_VALUE);
+    if (pInit) initial_value = pInit->GetArgN(0)->GetIntValue();
+    else initial_value = orig_min_value; // Default is min_value
+
+    MHParseNode *pPortion = p->GetNamedArg(C_INITIAL_PORTION);
+    if (pPortion) initial_portion = pPortion->GetArgN(0)->GetIntValue();
+    else initial_portion = orig_min_value-1; // Unset
+
+    MHParseNode *pStep = p->GetNamedArg(C_STEP_SIZE);
+    if (pStep) orig_step_size = pStep->GetArgN(0)->GetIntValue();
+    else orig_step_size = 1; // Unset
+
+    MHParseNode *pStyle = p->GetNamedArg(C_SLIDER_STYLE);
+    if (pStyle) m_style = (enum SliderStyle)pStyle->GetArgN(0)->GetEnumValue();
+    else m_style = SliderNormal;
+
+    MHParseNode *pslCol = p->GetNamedArg(C_SLIDER_REF_COLOUR);
+    if (pslCol) m_sliderRefColour.Initialise(pslCol->GetArgN(0), engine);
+    else engine->GetDefaultSliderRefColour(m_sliderRefColour);
 }
 
+static const char *rchOrientation[] =
+{
+    "left", // 1
+    "right",
+    "up",
+    "down" // 4
+};
+
+// Look up the Orientation. Returns zero if it doesn't match.  Used in the text parser only.
+int MHSlider::GetOrientation(const char *str)
+{
+    for (int i = 0; i < (int)(sizeof(rchOrientation)/sizeof(rchOrientation[0])); i++) {
+        if (strcasecmp(str, rchOrientation[i]) == 0) return (i+1); // Numbered from 1
+    }
+    return 0;
+}
+
+static const char *rchStyle[] =
+{
+    "normal", // 1
+    "thermometer",
+    "proportional" // 3
+};
+
+int MHSlider::GetStyle(const char *str)
+{
+    for (int i = 0; i < (int)(sizeof(rchStyle)/sizeof(rchStyle[0])); i++) {
+        if (strcasecmp(str, rchStyle[i]) == 0) return (i+1); // Numbered from 1
+    }
+    return 0;
+}
+
 void MHSlider::PrintMe(FILE *fd, int nTabs) const
 {
     PrintTabs(fd, nTabs); fprintf(fd, "{:Slider ");
-    MHVisible::PrintMe(fd, nTabs);
+    MHVisible::PrintMe(fd, nTabs+1);
     MHInteractible::PrintMe(fd, nTabs+1);
-    fprintf(fd, "****TODO\n");
+
+    PrintTabs(fd, nTabs); fprintf(fd, ":Orientation %s\n", rchOrientation[m_orientation-1]);
+
+    if (initial_value >= orig_min_value) {
+        PrintTabs(fd, nTabs+1); fprintf(fd, ":InitialValue %d\n", initial_value);
+    }
+
+    if (orig_min_value != 1) {
+        PrintTabs(fd, nTabs+1); fprintf(fd, ":MinValue %d\n", orig_min_value);
+    }
+
+    if (orig_max_value > orig_min_value) {
+        PrintTabs(fd, nTabs+1); fprintf(fd, ":MaxValue %d\n", orig_max_value);
+    }
+
+    if (initial_portion >= orig_min_value) {
+        PrintTabs(fd, nTabs+1); fprintf(fd, ":InitialPortion %d\n", initial_portion);
+    }
+
+    if (orig_step_size != 1) {
+        PrintTabs(fd, nTabs+1); fprintf(fd, ":StepSize %d\n", orig_step_size);
+    }
+
+    if (m_style != SliderNormal)
+    {
+        PrintTabs(fd, nTabs+1);
+        fprintf(fd, ":SliderStyle %s\n", rchStyle[m_style-1]);
+    }
+
+    if (m_sliderRefColour.IsSet()) {
+        PrintTabs(fd, nTabs+1);
+        fprintf(fd, ":SliderRefColour ");
+        m_sliderRefColour.PrintMe(fd, nTabs+2);
+        fprintf(fd, "\n");
+    }
+
     PrintTabs(fd, nTabs); fprintf(fd, "}\n");
 }
 
-MHEntryField::MHEntryField()
+// The MHEG standard doesn't define where the internal values are
+// initialised.  Assume it's during preparation.
+void MHSlider::Preparation(MHEngine *engine)
 {
+    MHVisible::Preparation(engine);
+    max_value = orig_max_value;
+    min_value = orig_min_value;
+    step_size = orig_step_size;
+    slider_value = initial_value;
+    portion = initial_portion;
+}
 
+void MHSlider::Display(MHEngine *engine)
+{
+    MHContext *d = engine->GetContext();
+    MHRgba colour;
+    if (m_fHighlightStatus && m_fEngineResp)
+        colour = GetColour(m_highlightRefColour);
+    else colour = GetColour(m_sliderRefColour);
+
+    int major; // Direction of change.
+    if (m_orientation == SliderLeft || m_orientation == SliderRight)
+        major = m_nBoxWidth;
+    else major = m_nBoxHeight;
+
+    if (max_value <= min_value) return; // Avoid divide by zero if error.
+
+    if (m_style == SliderNormal)
+    {
+        // This is drawn as a 9 pixel wide "thumb" at the position.
+        major -= 9; // Width of "thumb"
+        int posn = major * (slider_value-min_value) / (max_value-min_value);
+        switch (m_orientation)
+        {
+        case SliderLeft:
+            d->DrawRect(m_nPosX + posn, m_nPosY, 9, m_nBoxHeight, colour);
+            break;
+        case SliderRight:
+            d->DrawRect(m_nPosX + m_nBoxWidth - posn - 9, m_nPosY, 9, m_nBoxHeight, colour);
+            break;
+        case SliderUp:
+            d->DrawRect(m_nPosX, m_nPosY + m_nBoxHeight - posn - 9, m_nBoxWidth, 9, colour);
+            break;
+        case SliderDown:
+            d->DrawRect(m_nPosX, m_nPosY + posn, m_nBoxWidth, 9, colour);
+            break;
+        }
+    }
+    else {
+        // Thermometer and proportional sliders are drawn as bars.  Thermometers
+        // run from the start to the position, proportional sliders from the
+        // position for the "portion".
+        int start = 0;
+        int end = major * (slider_value-min_value) / (max_value-min_value);
+        if (m_style == SliderProp)
+        {
+            start = end;
+            end = major * (slider_value+portion -min_value) / (max_value-min_value);
+        }
+        switch (m_orientation)
+        {
+        case SliderLeft:
+            d->DrawRect(m_nPosX + start, m_nPosY, end-start, m_nBoxHeight, colour);
+            break;
+        case SliderRight:
+            d->DrawRect(m_nPosX + m_nBoxWidth - end, m_nPosY, end-start, m_nBoxHeight, colour);
+            break;
+        case SliderUp:
+            d->DrawRect(m_nPosX, m_nPosY + m_nBoxHeight - end, m_nBoxWidth, end-start, colour);
+            break;
+        case SliderDown:
+            d->DrawRect(m_nPosX, m_nPosY + start, m_nBoxWidth, end-start, colour);
+            break;
+        }
+
+    }
 }
 
+void MHSlider::Interaction(MHEngine *engine)
+{
+    MHInteractible::Interaction(engine);
+    // All the interaction is handled by KeyEvent.
+}
+
+// Called when the interaction has been terminated and we need
+// to restore the state to non-interacting.
+void MHSlider::InteractionCompleted(MHEngine *engine)
+{
+    MHInteractible::InteractionCompleted(engine);
+    // Redraw with the interaction highlighting turned off
+    engine->Redraw(GetVisibleArea());
+}
+
+// Called when a key is pressed.  The only keys that have an effect are
+// the Select and Cancel keys which both terminate the action and the
+// arrow keys.  The effect of the arrow keys depends on the orientation of
+// the slider.
+void MHSlider::KeyEvent(MHEngine *engine, int nCode)
+{
+    switch (nCode)
+    {
+    case 15: // Select key
+    case 16: // Cancel key
+        m_fInteractionStatus = false;
+        engine->SetInteraction(0);
+        InteractionCompleted(engine); // Interaction is interrupted.
+        engine->EventTriggered(this, EventInteractionCompleted);
+        break;
+
+    case 1: // Up
+        if (m_orientation == SliderUp)
+            Increment(engine);
+        else if (m_orientation == SliderDown)
+            Decrement(engine);
+        break;
+
+    case 2: // Down
+        if (m_orientation == SliderUp)
+            Decrement(engine);
+        else if (m_orientation == SliderDown)
+            Increment(engine);
+        break;
+
+    case 3: // Left
+        if (m_orientation == SliderLeft)
+            Increment(engine);
+        else if (m_orientation == SliderRight)
+            Decrement(engine);
+        break;
+
+    case 4: // Right
+        if (m_orientation == SliderLeft)
+            Decrement(engine);
+        else if (m_orientation == SliderRight)
+            Increment(engine);
+        break;
+
+    }
+}
+
+void MHSlider::Increment(MHEngine *engine)
+{
+    if (slider_value+step_size <= max_value)
+    {
+        slider_value += step_size;
+        engine->Redraw(GetVisibleArea());
+        engine->EventTriggered(this, EventSliderValueChanged);
+    }
+}
+
+void MHSlider::Decrement(MHEngine *engine)
+{
+    if (slider_value-step_size >= min_value)
+    {
+        slider_value -= step_size;
+        engine->Redraw(GetVisibleArea());
+        engine->EventTriggered(this, EventSliderValueChanged);
+    }
+}
+
+void MHSlider::Step(int nbSteps, MHEngine *engine)
+{
+    step_size = nbSteps;
+    if (m_fRunning) engine->Redraw(GetVisibleArea());
+    engine->EventTriggered(this, EventSliderValueChanged);
+}
+
+void MHSlider::SetSliderValue(int newValue, MHEngine *engine)
+{
+    slider_value = newValue;
+    if (m_fRunning) engine->Redraw(GetVisibleArea());
+    engine->EventTriggered(this, EventSliderValueChanged);
+}
+
+void MHSlider::SetPortion(int newPortion, MHEngine *engine)
+{
+    portion = newPortion;
+    if (m_fRunning) engine->Redraw(GetVisibleArea());
+    engine->EventTriggered(this, EventSliderValueChanged);
+}
+
+// Additional action defined in UK MHEG.
+void MHSlider::SetSliderParameters(int newMin, int newMax, int newStep, MHEngine *engine)
+{
+    min_value = newMin;
+    max_value = newMax;
+    step_size = newStep;
+    slider_value = newMin;
+    if (m_fRunning) engine->Redraw(GetVisibleArea());
+    engine->EventTriggered(this, EventSliderValueChanged);
+}
+
+
+MHEntryField::MHEntryField(): MHInteractible(this)
+{
+
+}
+
 MHEntryField::~MHEntryField()
 {
 
