Index: mythbuild/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
+++ mythbuild/mythtv/libs/libmythtv/NuppelVideoPlayer.cpp
@@ -60,6 +60,8 @@ using namespace std;
 #include "mythverbose.h"
 #include "myth_imgconvert.h"
 
+#include "grideditcutpoints.h"
+
 extern "C" {
 #include "vbitext/vbi.h"
 #include "vsync.h"
@@ -89,6 +91,10 @@ static unsigned dbg_ident(const NuppelVi
 #define LOC_WARN QString("NVP(%1), Warning: ").arg(dbg_ident(this),0,36)
 #define LOC_ERR  QString("NVP(%1), Error: ").arg(dbg_ident(this),0,36)
 
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
 uint track_type_to_display_mode[kTrackTypeCount+2] =
 {
     kDisplayNone,
@@ -117,6 +123,7 @@ NuppelVideoPlayer::NuppelVideoPlayer(boo
       decoder_thread_alive(true),   killplayer(false),
       killvideo(false),             livetv(false),
       watchingrecording(false),     editmode(false),
+      hideedits(false),
       resetvideo(false),            using_null_videoout(false),
       no_audio_in(false),           no_audio_out(false),
       transcoding(false),
@@ -128,7 +135,9 @@ NuppelVideoPlayer::NuppelVideoPlayer(boo
       bookmarkseek(0),
       // Seek
       fftime(0),                    seekamountpos(4),
-      seekamount(30),               exactseeks(false),
+      allow_pagesize(false),        half_page(0),
+      seekamount(30),               seekamounttext("30 Frames"),
+      exactseeks(false),
       // Playback misc.
       videobuf_retries(0),          framesPlayed(0),
       totalFrames(0),               totalLength(0),
@@ -179,6 +188,13 @@ NuppelVideoPlayer::NuppelVideoPlayer(boo
       yuv_need_copy(false),         yuv_desired_size(0,0),
       yuv_scaler(NULL),             yuv_frame_scaled(NULL),
       yuv_scaler_in_size(0,0),      yuv_scaler_out_size(0,0),
+      // Grid Editing
+      grid_edit_image_scaler(NULL), grid_edit_image_small_scaler(NULL),
+      grid_edit_image_buffer_yuv(NULL), grid_edit_image_small_buffer_yuv(NULL),
+      grid_edit_image_buffer_rgb(NULL), grid_edit_image_small_buffer_rgb(NULL),
+      grid_edit_image_buffer_length(0), grid_edit_image_small_buffer_length(0),
+      grid_edit_image_in_size(-1, -1),
+
       // Filters
       videoFiltersForProgram(""),   videoFiltersOverride(""),
       postfilt_width(0),            postfilt_height(0),
@@ -338,6 +354,8 @@ NuppelVideoPlayer::~NuppelVideoPlayer(vo
         output_jmeter = NULL;
     }
 
+    ClearScreenGrab();
+
     ShutdownYUVResize();
 }
 
@@ -1594,6 +1612,215 @@ VideoFrame *NuppelVideoPlayer::GetCurren
     return retval;
 }
 
+bool NuppelVideoPlayer::GetScreenGrabsOfCurrentFrame(QImage &normal, QImage &small)
+{
+    FUNCTIONLOGGER;
+    unsigned char *data      = NULL;
+    VideoFrame    *frame     = NULL;
+    AVPicture      orig;
+    AVPicture      scaled_yuv;
+    AVPicture      scaled_rgb;
+    AVPicture      scaled_yuv_small;
+    AVPicture      scaled_rgb_small;
+    bzero(&orig,         sizeof(AVPicture));
+    bzero(&scaled_yuv,       sizeof(AVPicture));
+    bzero(&scaled_rgb,       sizeof(AVPicture));
+    bzero(&scaled_yuv_small, sizeof(AVPicture));
+    bzero(&scaled_rgb_small, sizeof(AVPicture));
+
+    int vw, vh;
+    if (!(frame = GetCurrentFrame(vw, vh)))
+    {
+        return false;
+    }
+
+    if (!(data = frame->buf))
+    {
+        ReleaseCurrentFrame(frame);
+        return false;
+    }
+
+    {
+        QMutexLocker l(&videofiltersLock);
+
+        // Check to see if the screen dimensions have changed
+        // Probably shouldn't happen in normal use.
+        if (grid_edit_image_in_size != video_dim)
+            SetupScreenGrab();
+
+        avpicture_fill(&orig, data, PIX_FMT_YUV420P,
+                       video_dim.width(), video_dim.height());
+
+        avpicture_deinterlace(&orig, &orig, PIX_FMT_YUV420P,
+                              video_dim.width(), video_dim.height());
+
+        // Rescale to the normal size
+        avpicture_fill(&scaled_yuv, grid_edit_image_buffer_yuv, PIX_FMT_YUV420P,
+                       grid_edit_image_size.width(),
+                       grid_edit_image_size.height());
+
+        avpicture_fill(&scaled_rgb, grid_edit_image_buffer_rgb, PIX_FMT_RGB32,
+                       grid_edit_image_size.width(),
+                       grid_edit_image_size.height());
+
+        sws_scale(grid_edit_image_scaler, orig.data, orig.linesize, 0,
+                  video_dim.height(),
+                  scaled_yuv.data, scaled_yuv.linesize);
+
+        myth_sws_img_convert(
+            &scaled_rgb, PIX_FMT_RGB32, &scaled_yuv, PIX_FMT_YUV420P,
+                    grid_edit_image_size.width(), grid_edit_image_size.height());
+
+
+        // Rescale to the small size
+        avpicture_fill(&scaled_yuv_small, grid_edit_image_small_buffer_yuv, PIX_FMT_YUV420P,
+                       grid_edit_image_small_size.width(),
+                       grid_edit_image_small_size.height());
+
+        avpicture_fill(&scaled_rgb_small, grid_edit_image_small_buffer_rgb, PIX_FMT_RGB32,
+                       grid_edit_image_small_size.width(),
+                       grid_edit_image_small_size.height());
+
+        sws_scale(grid_edit_image_small_scaler, orig.data, orig.linesize, 0,
+                  video_dim.height(),
+                  scaled_yuv_small.data, scaled_yuv_small.linesize);
+
+        myth_sws_img_convert(
+            &scaled_rgb_small, PIX_FMT_RGB32, &scaled_yuv_small, PIX_FMT_YUV420P,
+                    grid_edit_image_small_size.width(), grid_edit_image_small_size.height());
+
+    }
+
+    // Don't need the current frame anymore so release it
+    ReleaseCurrentFrame(frame);
+
+
+#ifdef MMX
+#define _RGBSWAP .rgbSwapped()
+#endif
+
+    normal = QImage(grid_edit_image_buffer_rgb,
+                    grid_edit_image_size.width(), grid_edit_image_size.height(),
+                    QImage::Format_RGB32)_RGBSWAP;
+
+    small = QImage(grid_edit_image_small_buffer_rgb,
+                   grid_edit_image_small_size.width(), grid_edit_image_small_size.height(),
+                   QImage::Format_RGB32)_RGBSWAP;
+
+    return true;
+}
+
+void NuppelVideoPlayer::SetScreenGrabSizes(QSize normal, QSize small)
+{
+    grid_edit_image_size = normal;
+    grid_edit_image_small_size = small;
+
+    SetupScreenGrab();
+    VERBOSE(VB_GENERAL, QString("Main Image = (%1, %2) from (%3, %4)")
+                        .arg(grid_edit_image_size.width())
+                        .arg(grid_edit_image_size.height())
+                        .arg(normal.width()).arg(normal.height()));
+    VERBOSE(VB_GENERAL, QString("Small Image = (%1, %2) from (%3, %4)")
+                        .arg(grid_edit_image_small_size.width())
+                        .arg(grid_edit_image_small_size.height())
+                        .arg(small.width()).arg(small.height()));
+
+}
+
+void NuppelVideoPlayer::ClearScreenGrab()
+{
+    if (grid_edit_image_scaler)
+    {
+        sws_freeContext(grid_edit_image_scaler);
+        grid_edit_image_scaler = NULL;
+    }
+
+    if (grid_edit_image_small_scaler)
+    {
+        sws_freeContext(grid_edit_image_small_scaler);
+        grid_edit_image_small_scaler=NULL;
+    }
+
+    if (grid_edit_image_buffer_yuv)
+    {
+        delete grid_edit_image_buffer_yuv;
+        grid_edit_image_buffer_yuv=NULL;
+    }
+
+    if (grid_edit_image_small_buffer_yuv)
+    {
+        delete grid_edit_image_small_buffer_yuv;
+        grid_edit_image_small_buffer_yuv=NULL;
+    }
+
+    if (grid_edit_image_buffer_rgb)
+    {
+        delete grid_edit_image_buffer_rgb;
+        grid_edit_image_buffer_rgb=NULL;
+    }
+
+    if (grid_edit_image_small_buffer_rgb)
+    {
+        delete grid_edit_image_small_buffer_rgb;
+        grid_edit_image_small_buffer_rgb=NULL;
+    }
+}
+
+void NuppelVideoPlayer::SetupScreenGrab()
+{
+    ClearScreenGrab();
+    grid_edit_image_in_size = video_dim;
+
+    // Normalize the output sizes.
+    // This is necessary to preserve the aspect ratio
+
+    QSize tmpsize = grid_edit_image_size;
+    grid_edit_image_size =  video_dim;
+    grid_edit_image_size.scale(tmpsize, Qt::KeepAspectRatio);
+
+    tmpsize = grid_edit_image_small_size;
+    grid_edit_image_small_size = video_dim;
+    grid_edit_image_small_size.scale(tmpsize, Qt::KeepAspectRatio);
+
+    grid_edit_image_size = QSize(grid_edit_image_size.width() & ~0x7,
+                                 grid_edit_image_size.height() & ~0x7);
+    grid_edit_image_small_size = QSize(grid_edit_image_small_size.width() & ~0x7,
+                                       grid_edit_image_small_size.height() & ~0x7);
+
+    // Do normal first
+    uint sz = grid_edit_image_size.width() * grid_edit_image_size.height();
+    grid_edit_image_buffer_yuv = new unsigned char[(sz * 3 / 2) + 128];
+    grid_edit_image_buffer_rgb = new unsigned char[sz * 4 + 128];
+
+    //grid_edit_image_scaler = img_resample_init(
+    //            grid_edit_image_size.width(), grid_edit_image_size.height(),
+    //            grid_edit_image_in_size.width(),  grid_edit_image_in_size.height());
+
+    grid_edit_image_scaler = sws_getCachedContext(grid_edit_image_scaler,
+                grid_edit_image_in_size.width(), grid_edit_image_in_size.height(),
+                PIX_FMT_YUV420P,
+                grid_edit_image_size.width(),  grid_edit_image_size.height(),
+                PIX_FMT_YUV420P,
+                SWS_FAST_BILINEAR, NULL, NULL, NULL);
+
+    // Then small
+    sz = grid_edit_image_small_size.width() * grid_edit_image_small_size.height();
+    grid_edit_image_small_buffer_yuv = new unsigned char[(sz * 3 / 2) + 128];
+    grid_edit_image_small_buffer_rgb = new unsigned char[sz * 4 + 128];
+
+    // Resize from normal to small
+    //grid_edit_image_small_scaler = img_resample_init(
+    //            grid_edit_image_small_size.width(), grid_edit_image_small_size.height(),
+    //            grid_edit_image_size.width(),  grid_edit_image_size.height());
+
+    grid_edit_image_small_scaler = sws_getCachedContext(grid_edit_image_small_scaler,
+                grid_edit_image_in_size.width(), grid_edit_image_in_size.height(),
+                PIX_FMT_YUV420P,
+                grid_edit_image_small_size.width(),  grid_edit_image_small_size.height(),
+                PIX_FMT_YUV420P,
+                SWS_FAST_BILINEAR, NULL, NULL, NULL);
+}
+
 void NuppelVideoPlayer::ReleaseCurrentFrame(VideoFrame *frame)
 {
     if (frame)
@@ -4855,6 +5082,8 @@ bool NuppelVideoPlayer::EnableEdit(void)
 
     dialogname = "";
 
+    osd->HideAllExcept("editmode");
+
     InfoMap infoMap;
     player_ctx->LockPlayingInfo(__FILE__, __LINE__);
     player_ctx->playingInfo->ToMap(infoMap);
@@ -4919,8 +5148,81 @@ void NuppelVideoPlayer::DisableEdit(void
     player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
 }
 
+bool NuppelVideoPlayer::EditSeekToFrame(long long targetFrame)
+{
+    bool tmpexactseeks = exactseeks;
+    GetDecoder()->setExactSeeks(true);
+
+//    VERBOSE(VB_GENERAL, QString("Before current frame = %1, going to frame %2")
+//           .arg(GetFramesPlayed())
+//           .arg(targetFrame));
+
+    if (framesPlayed > targetFrame)
+    {
+        // seek back
+        rewindtime = framesPlayed - targetFrame;
+        while (rewindtime != 0)
+            usleep(1000);
+    }
+    else
+    {
+        // seek forward
+        fftime = targetFrame - framesPlayed;
+        while (fftime != 0)
+            usleep(1000);
+
+    }
+//    VERBOSE(VB_GENERAL, QString("After current frame = %1")
+//           .arg(GetFramesPlayed()));
+    GetDecoder()->setExactSeeks(tmpexactseeks);
+    return (targetFrame == framesPlayed);
+}
+
+void NuppelVideoPlayer::EditHandleClearMap()
+{
+    QMap<long long, int>::Iterator it;
+    for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
+        osd->HideEditArrow(it.key(), *it);
+
+    deleteMap.clear();
+    UpdateEditSlider();
+}
+
+void NuppelVideoPlayer::EditHandleInvertMap()
+{
+    QMap<long long, int>::Iterator it;
+    for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
+        ReverseMark(it.key());
+
+    UpdateEditSlider();
+    UpdateTimeDisplay();
+}
+
+void NuppelVideoPlayer::EditHandleLoadCommSkip()
+{
+    if (hascommbreaktable)
+    {
+        commBreakMapLock.lock();
+        QMap<long long, int>::Iterator it;
+        for (it = commBreakMap.begin(); it != commBreakMap.end(); ++it)
+        {
+            if (!deleteMap.contains(it.key()))
+            {
+                if (*it == MARK_COMM_START)
+                    AddMark(it.key(), MARK_CUT_START);
+                else
+                    AddMark(it.key(), MARK_CUT_END);
+            }
+        }
+        commBreakMapLock.unlock();
+        UpdateEditSlider();
+        UpdateTimeDisplay();
+    }
+}
+
 bool NuppelVideoPlayer::DoKeypress(QKeyEvent *e)
 {
+
     bool handled = false;
     QStringList actions;
     handled = gContext->GetMainWindow()->TranslateKeyPress("TV Editing", e, actions);
@@ -5001,44 +5303,11 @@ bool NuppelVideoPlayer::DoKeypress(QKeyE
             UpdateTimeDisplay();
         }
         else if (action == "CLEARMAP")
-        {
-            QMap<long long, int>::Iterator it;
-            for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
-                osd->HideEditArrow(it.key(), *it);
-
-            deleteMap.clear();
-            UpdateEditSlider();
-        }
+            EditHandleClearMap();
         else if (action == "INVERTMAP")
-        {
-            QMap<long long, int>::Iterator it;
-            for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
-                ReverseMark(it.key());
-
-            UpdateEditSlider();
-            UpdateTimeDisplay();
-        }
+            EditHandleInvertMap();
         else if (action == "LOADCOMMSKIP")
-        {
-            if (hascommbreaktable)
-            {
-                commBreakMapLock.lock();
-                QMap<long long, int>::Iterator it;
-                for (it = commBreakMap.begin(); it != commBreakMap.end(); ++it)
-                {
-                    if (!deleteMap.contains(it.key()))
-                    {
-                        if (*it == MARK_COMM_START)
-                            AddMark(it.key(), MARK_CUT_START);
-                        else
-                            AddMark(it.key(), MARK_CUT_END);
-                    }
-                }
-                commBreakMapLock.unlock();
-                UpdateEditSlider();
-                UpdateTimeDisplay();
-            }
-        }
+            EditHandleLoadCommSkip();
         else if (action == "PREVCUT")
         {
             int old_seekamount = seekamount;
@@ -5084,8 +5353,11 @@ bool NuppelVideoPlayer::DoKeypress(QKeyE
             UpdateEditSlider();
             UpdateTimeDisplay();
         }
-        else if (action == "ESCAPE" || action == "MENU" ||
-                 action == "EDIT")
+        else if (action == "EDIT" || action == "MENU")
+        {
+            m_tv->ShowEditRecordingGrid();
+        }
+        else if (action == "ESCAPE")
         {
             DisableEdit();
             retval = false;
@@ -5098,6 +5370,54 @@ bool NuppelVideoPlayer::DoKeypress(QKeyE
     return retval;
 }
 
+void NuppelVideoPlayer::StartEditRecordingGrid(void)
+{
+    {
+        QMutexLocker l(&grid_edit_lock);
+        if (hideedits)
+            return;
+
+        hideedits = true;
+    }
+
+    // Completely hide the OSD
+    osd->HideAll();
+}
+
+void NuppelVideoPlayer::EndEditRecordingGrid(void)
+{
+
+    ClearScreenGrab();
+
+    allow_pagesize = false;
+    {
+        QMutexLocker l(&grid_edit_lock);
+
+        hideedits = false;
+
+        // Show OSD
+
+        InfoMap infoMap;
+        player_ctx->LockPlayingInfo(__FILE__, __LINE__);
+        player_ctx->playingInfo->ToMap(infoMap);
+        infoMap.detach();
+        player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+
+        osd->SetText("editmode", infoMap, -1);
+
+        UpdateEditSlider();
+        UpdateTimeDisplay();
+        if (seekamountpos == 3 || seekamountpos == 4)
+            UpdateSeekAmount(true);
+        else
+            UpdateSeekAmountDisplay();
+
+        QMap<long long, int>::Iterator it;
+        for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
+             AddMark(it.key(), *it);
+    }
+}
+
 AspectOverrideMode NuppelVideoPlayer::GetAspectOverride(void) const
 {
     if (videoOutput)
@@ -5184,31 +5504,74 @@ bool NuppelVideoPlayer::IsEmbedding(void
 
 void NuppelVideoPlayer::UpdateSeekAmount(bool up)
 {
-    if (seekamountpos > 0 && !up)
-        seekamountpos--;
-    if (seekamountpos < 9 && up)
-        seekamountpos++;
+    if (up)
+    {
+        if (seekamountpos < 11)
+            seekamountpos++;
+        if (allow_pagesize)
+        {
+            if (seekamountpos == 1)
+                seekamountpos = 2;
+        }
+        else
+        {
+            if (seekamountpos == 3 || seekamountpos == 4)
+                seekamountpos = 5;
+        }
+    }
+    else
+    {
+        if (seekamountpos > 0)
+            seekamountpos--;
+        if (allow_pagesize)
+        {
+            if (seekamountpos == 1)
+                seekamountpos =0;
+        }
+        else
+        {
+            if (seekamountpos == 3 || seekamountpos == 4)
+                seekamountpos = 2;
+        }
+    }
 
     QString text = "";
 
     switch (seekamountpos)
     {
-        case 0: text = QObject::tr("cut point"); seekamount = -2; break;
-        case 1: text = QObject::tr("keyframe"); seekamount = -1; break;
-        case 2: text = QObject::tr("1 frame"); seekamount = 1; break;
-        case 3: text = QObject::tr("0.5 seconds"); seekamount = (int)roundf(video_frame_rate / 2); break;
-        case 4: text = QObject::tr("%n second(s)", "", 1); seekamount = (int)roundf(video_frame_rate); break;
-        case 5: text = QObject::tr("%n second(s)", "", 5); seekamount = (int)roundf(video_frame_rate * 5); break;
-        case 6: text = QObject::tr("%n second(s)", "", 20); seekamount = (int)roundf(video_frame_rate * 20); break;
-        case 7: text = QObject::tr("%n minute(s)", "", 1); seekamount = (int)roundf(video_frame_rate * 60); break;
-        case 8: text = QObject::tr("%n minute(s)", "", 5); seekamount = (int)roundf(video_frame_rate * 300); break;
-        case 9: text = QObject::tr("%n minute(s)", "", 10); seekamount = (int)roundf(video_frame_rate * 600); break;
+        case  0: text = QObject::tr("cut point"); seekamount = -2; break;
+
+        // Only for non-edit grid
+        case  1: text = QObject::tr("keyframe"); seekamount = -1; break;
+        // Only for non-edit grid
+
+        case  2: text = QObject::tr("1 frame"); seekamount = 1; break;
+
+        // Case 3 & 4 are for the edit grid only
+        case  3: text = QObject::tr("1/2 Page");    seekamount =  half_page; break;
+        case  4: text = QObject::tr("Full Page");   seekamount =  2*half_page; break;
+        // Case 3 & 4 are for the edit grid only
+
+        case  5: text = QObject::tr("0.5 seconds"); seekamount = (int)roundf(video_frame_rate / 2); break;
+        case  6: text = QObject::tr("%n second(s)", "", 1); seekamount = (int)roundf(video_frame_rate); break;
+        case  7: text = QObject::tr("%n second(s)", "", 5); seekamount = (int)roundf(video_frame_rate * 5); break;
+        case  8: text = QObject::tr("%n second(s)", "", 20); seekamount = (int)roundf(video_frame_rate * 20); break;
+        case  9: text = QObject::tr("%n minute(s)", "", 1); seekamount = (int)roundf(video_frame_rate * 60); break;
+        case 10: text = QObject::tr("%n minute(s)", "", 5); seekamount = (int)roundf(video_frame_rate * 300); break;
+        case 11: text = QObject::tr("%n minute(s)", "", 10); seekamount = (int)roundf(video_frame_rate * 600); break;
         default: text = QObject::tr("error"); seekamount = (int)roundf(video_frame_rate); break;
     }
 
+    seekamounttext = text;
+    UpdateSeekAmountDisplay();
+}
+
+void NuppelVideoPlayer::UpdateSeekAmountDisplay(void)
+{
     InfoMap infoMap;
-    infoMap["seekamount"] = text;
-    osd->SetText("editmode", infoMap, -1);
+    infoMap["seekamount"] = seekamounttext;
+    if (!hideedits)
+        osd->SetText("editmode", infoMap, -1);
 }
 
 void NuppelVideoPlayer::UpdateTimeDisplay(void)
@@ -5238,50 +5601,56 @@ void NuppelVideoPlayer::UpdateTimeDispla
     infoMap["timedisplay"] = timestr;
     infoMap["framedisplay"] = framestr;
     infoMap["cutindicator"] = cutmarker;
-    osd->SetText("editmode", infoMap, -1);
+    if (!hideedits)
+        osd->SetText("editmode", infoMap, -1);
 }
 
-void NuppelVideoPlayer::HandleSelect(bool allowSelectNear)
+DeletePointInfo NuppelVideoPlayer::GetDeletePointInfo(long long frame, bool allowSelectNear)
 {
-    bool deletepoint = false;
-    bool cut_after = false;
-    int direction = 0;
+    DeletePointInfo retval;
 
     if(!deleteMap.isEmpty())
     {
         QMap<long long, int>::ConstIterator iter = deleteMap.begin();
 
-        while((iter != deleteMap.end()) && (iter.key() < framesPlayed))
+        while((iter != deleteMap.end()) && (iter.key() < frame))
             ++iter;
 
         if (iter == deleteMap.end())
         {
             --iter;
-            cut_after = !(*iter);
+            retval.cut_after = !(*iter);
         }
-        else if((iter != deleteMap.begin()) && (iter.key() != framesPlayed))
+        else if((iter != deleteMap.begin()) && (iter.key() != frame))
         {
             long long value = iter.key();
-            if((framesPlayed - (--iter).key()) > (value - framesPlayed))
+            if((framesPlayed - (--iter).key()) > (value - frame))
             {
-                cut_after = !(*iter);
+                retval.cut_after = !(*iter);
                 ++iter;
             }
             else
-                cut_after = !(*iter);
+                retval.cut_after = !(*iter);
         }
 
-        direction = (*iter);
-        deleteframe = iter.key();
+        retval.direction = (*iter);
+        retval.deleteframe = iter.key();
 
-        if ((absLongLong(deleteframe - framesPlayed) <
+        if ((absLongLong(retval.deleteframe - frame) <
                    (int)ceil(20 * video_frame_rate)) && !allowSelectNear)
         {
-            deletepoint = true;
+            retval.deletepoint = true;
         }
     }
+    return retval;
+}
+
+void NuppelVideoPlayer::HandleSelect(bool allowSelectNear)
+{
+    DeletePointInfo dpi = GetDeletePointInfo(framesPlayed, allowSelectNear);
+    deleteframe = dpi.deleteframe;
 
-    if (deletepoint)
+    if (dpi.deletepoint)
     {
         QString message = QObject::tr("You are close to an existing cut point. "
                                       "Would you like to:");
@@ -5289,7 +5658,7 @@ void NuppelVideoPlayer::HandleSelect(boo
         QString option2 = QObject::tr("Move this cut point to the current "
                                       "position");
         QString option3 = QObject::tr("Flip directions - delete to the ");
-        if (direction == 0)
+        if (dpi.direction == 0)
             option3 += QObject::tr("right");
         else
             option3 += QObject::tr("left");
@@ -5319,7 +5688,7 @@ void NuppelVideoPlayer::HandleSelect(boo
         options += option1;
         options += option2;
 
-        osd->NewDialogBox(dialogname, message, options, -1, cut_after);
+        osd->NewDialogBox(dialogname, message, options, -1, dpi.cut_after);
     }
 }
 
@@ -5370,68 +5739,86 @@ void NuppelVideoPlayer::HandleResponse(v
 
 void NuppelVideoPlayer::UpdateEditSlider(void)
 {
-    osd->DoEditSlider(deleteMap, framesPlayed, totalFrames);
+    if (!hideedits)
+        osd->DoEditSlider(deleteMap, framesPlayed, totalFrames);
 }
 
 void NuppelVideoPlayer::AddMark(long long frames, int type)
 {
     deleteMap[frames] = type;
-    osd->ShowEditArrow(frames, totalFrames, type);
+    if (!hideedits)
+        osd->ShowEditArrow(frames, totalFrames, type);
 }
 
 void NuppelVideoPlayer::DeleteMark(long long frames)
 {
-    osd->HideEditArrow(frames, deleteMap[frames]);
+    if (!hideedits)
+        osd->HideEditArrow(frames, deleteMap[frames]);
     deleteMap.remove(frames);
 }
 
 void NuppelVideoPlayer::ReverseMark(long long frames)
 {
-    osd->HideEditArrow(frames, deleteMap[frames]);
+    if (!hideedits)
+        osd->HideEditArrow(frames, deleteMap[frames]);
 
     if (deleteMap[frames] == MARK_CUT_END)
         deleteMap[frames] = MARK_CUT_START;
     else
         deleteMap[frames] = MARK_CUT_END;
 
-    osd->ShowEditArrow(frames, totalFrames, deleteMap[frames]);
+    if (!hideedits)
+        osd->ShowEditArrow(frames, totalFrames, deleteMap[frames]);
 }
 
-void NuppelVideoPlayer::HandleArbSeek(bool right)
+long long NuppelVideoPlayer::CalcCutPointSeek(long long baseframe, bool right)
 {
-    if (seekamount == -2)
+    QMap<long long, int>::Iterator i = deleteMap.begin();
+    long long framenum = -1;
+    long long seekcount = 0;
+    if (right)
     {
-        QMap<long long, int>::Iterator i = deleteMap.begin();
-        long long framenum = -1;
-        if (right)
+        for (; i != deleteMap.end(); ++i)
         {
-            for (; i != deleteMap.end(); ++i)
+            if (i.key() > baseframe)
             {
-                if (i.key() > framesPlayed)
-                {
-                    framenum = i.key();
-                    break;
-                }
+                framenum = i.key();
+                break;
             }
-            if (framenum == -1)
-                framenum = totalFrames;
+        }
+        if (framenum == -1)
+            framenum = totalFrames;
+        seekcount = framenum - baseframe;
+    }
+    else
+    {
+        for (; i != deleteMap.end(); ++i)
+        {
+            if (i.key() >= baseframe)
+                break;
+            framenum = i.key();
+        }
+        if (framenum == -1)
+            framenum = 0;
+        seekcount = baseframe - framenum;
+    }
+    return seekcount;
+}
 
-            fftime = framenum - framesPlayed;
+void NuppelVideoPlayer::HandleArbSeek(bool right)
+{
+    if (seekamount == -2)
+    {
+        long long seekcount = CalcCutPointSeek(framesPlayed, right);
+        if (right)
+        {
+            fftime = seekcount;
             while (fftime > 0)
                 usleep(1000);
         }
         else
         {
-            for (; i != deleteMap.end(); ++i)
-            {
-                if (i.key() >= framesPlayed)
-                    break;
-                framenum = i.key();
-            }
-            if (framenum == -1)
-                framenum = 0;
-
-            rewindtime = framesPlayed - framenum;
+            rewindtime = seekcount;
             while (rewindtime > 0)
                 usleep(1000);
         }
@@ -5462,6 +5849,27 @@ void NuppelVideoPlayer::HandleArbSeek(bo
     UpdateEditSlider();
 }
 
+int NuppelVideoPlayer::GetCutStatus(long long testframe) const
+{
+    int retval = 0;
+    QMap<long long, int>::const_iterator i;
+    i = deleteMap.find(testframe);
+    if (i == deleteMap.end()) {
+        // testframe is not an explicit cutpoint
+        // See if it is in a deleted area
+        if (IsInDelete(testframe))
+            retval = 1;
+    } else {
+        int direction = *i;
+        if (direction == 0)
+            retval = 2;
+        else
+            retval = 3;
+    }
+
+    return retval;
+}
+
 bool NuppelVideoPlayer::IsInDelete(long long testframe) const
 {
     long long startpos = 0;
Index: mythbuild/mythtv/libs/libmythtv/NuppelVideoPlayer.h
===================================================================
--- mythbuild.orig/mythtv/libs/libmythtv/NuppelVideoPlayer.h
+++ mythbuild/mythtv/libs/libmythtv/NuppelVideoPlayer.h
@@ -84,9 +84,23 @@ enum
     kDisplayTeletextMenu        = 0x40,
 };
 
+class DeletePointInfo
+{
+    public:
+        DeletePointInfo() :
+            deleteframe(0), deletepoint(false), cut_after(false), direction(0) {}
+
+        long long deleteframe;
+        bool deletepoint;
+        bool cut_after;
+        int direction;
+};
+
 class MPUBLIC NuppelVideoPlayer : public CC608Reader, public CC708Reader
 {
     friend class PlayerContext;
+    friend class GridEditCutpoints;
+    friend class TV;
 
   public:
     NuppelVideoPlayer(bool muted = false);
@@ -279,6 +293,7 @@ class MPUBLIC NuppelVideoPlayer : public
     bool EnableEdit(void);
     bool DoKeypress(QKeyEvent *e);
     bool GetEditMode(void) const { return editmode; }
+    bool GetHideEdits(void) const { return hideedits; }
 
     // Decoder stuff..
     VideoFrame *GetNextVideoFrame(bool allow_unsafe = true);
@@ -298,6 +313,18 @@ class MPUBLIC NuppelVideoPlayer : public
     void ShutdownYUVResize(void);
     void SaveScreenshot(void);
 
+    // Edit stuff
+    bool EditSeekToFrame(long long targetFrame);
+    void SetScreenGrabSizes(QSize normal, QSize small);
+    bool GetScreenGrabsOfCurrentFrame(QImage & normal, QImage &small); // Get current frame
+
+    void       EditHandleClearMap();
+    void       EditHandleInvertMap();
+    void       EditHandleLoadCommSkip();
+
+    void StartEditRecordingGrid();
+    void EndEditRecordingGrid();
+
     // Reinit
     void    ReinitOSD(void);
     void    ReinitVideo(void);
@@ -425,6 +452,17 @@ class MPUBLIC NuppelVideoPlayer : public
     bool PosMapFromEnc(unsigned long long          start,
                        QMap<long long, long long> &posMap);
 
+    // Stuff for GridEditCutpoints
+    long long CalcCutPointSeek(long long baseframe, bool right);
+    // returns
+    // 0 - no cut
+    // 1 - is deleted
+    // 2 - cut left
+    // 3 - cut right
+    int  GetCutStatus(long long testframe) const;
+    long long GetSeekAmount() { return seekamount; }
+    QString GetSeekAmountText() { return seekamounttext; }
+
   protected:
     void DisplayPauseFrame(void);
     void DisplayNormalFrame(void);
@@ -510,8 +548,16 @@ class MPUBLIC NuppelVideoPlayer : public
     void HandleSelect(bool allowSelectNear = false);
     void HandleResponse(void);
 
+    DeletePointInfo GetDeletePointInfo(long long frame, bool allowSelectNear);
+
+    void SetupScreenGrab();
+    void ClearScreenGrab();
+
     void UpdateTimeDisplay(void);
+    int  GetSeekAmountPos() { return seekamountpos; }
     void UpdateSeekAmount(bool up);
+    void SetHalfPageSize(int hp) { allow_pagesize = true; half_page = hp; }
+    void UpdateSeekAmountDisplay(void);
     void UpdateEditSlider(void);
 
     // Private A/V Sync Stuff
@@ -585,6 +631,7 @@ class MPUBLIC NuppelVideoPlayer : public
     bool     livetv;
     bool     watchingrecording;
     bool     editmode;
+    bool     hideedits;
     bool     resetvideo;
     bool     using_null_videoout;
     bool     no_audio_in;
@@ -608,9 +655,13 @@ class MPUBLIC NuppelVideoPlayer : public
     long long fftime;
     /// 1..9 == keyframe..10 minutes. 0 == cut point
     int       seekamountpos;
+    ///  Used for Grid Edit logic
+    bool      allow_pagesize;
+    int       half_page;
     /// Seekable frame increment when not using exact seeks.
     /// Usually equal to keyframedist.
     int      seekamount;
+    QString  seekamounttext; // OSD seek units
     /// Iff true we ignore seek amount and try to seek to an
     /// exact frame ignoring key frame restrictions.
     bool     exactseeks;
@@ -750,6 +801,20 @@ class MPUBLIC NuppelVideoPlayer : public
     QMutex              yuv_lock;
     QWaitCondition      yuv_wait;
 
+    // EditGrid still image capture
+    struct SwsContext  *grid_edit_image_scaler;
+    struct SwsContext  *grid_edit_image_small_scaler;
+    unsigned char      *grid_edit_image_buffer_yuv;
+    unsigned char      *grid_edit_image_small_buffer_yuv;
+    unsigned char      *grid_edit_image_buffer_rgb;
+    unsigned char      *grid_edit_image_small_buffer_rgb;
+    int                 grid_edit_image_buffer_length;
+    int                 grid_edit_image_small_buffer_length;
+    QSize               grid_edit_image_in_size;
+    QSize               grid_edit_image_size;
+    QSize               grid_edit_image_small_size;
+    QMutex              grid_edit_lock;
+
     // Filters
     QMutex   videofiltersLock;
     QString  videoFiltersForProgram;
Index: mythbuild/mythtv/libs/libmythtv/grideditcutpoints.cpp
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythtv/grideditcutpoints.cpp
@@ -0,0 +1,830 @@
+#include <math.h>
+#include <unistd.h>
+#include <iostream>
+#include <algorithm>
+using namespace std;
+
+#include <QApplication>
+#include <QPainter>
+#include <QFont>
+#include <QKeyEvent>
+#include <QEvent>
+#include <QPixmap>
+#include <QPaintEvent>
+#include <QCursor>
+#include <QImage>
+#include <QLayout>
+#include <QLabel>
+#include <QDateTime>
+#include <QRect>
+
+#include "mythcontext.h"
+#include "mythdbcon.h"
+#include "grideditcutpoints.h"
+#include "grideditimages.h"
+#include "NuppelVideoPlayer.h"
+#include "mythuicutpointbar.h"
+#include "mythuicutpointimage.h"
+#include "mythdialogbox.h"
+#include "tv_play.h"
+
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
+void GridEditCutpoints::RunGridEditCutpoints(TV *tv)
+{
+    FUNCTIONLOGGER;
+    MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();
+
+    GridEditCutpoints *er = new GridEditCutpoints(mainStack, "editrecording", tv);
+
+    if (er->Create())
+    {
+        mainStack->AddScreen(er);
+    }
+    else
+    {
+        VERBOSE(VB_GENERAL, "Create failed");
+        delete er;
+    }
+}
+
+GridEditCutpoints::GridEditCutpoints(MythScreenStack *parent, QString name, TV *tv)
+         : MythScreenType(parent, name, true)
+{
+    m_tv = tv;
+
+    const PlayerContext *mctx =
+        m_tv->GetPlayerReadLock(0, __FILE__, __LINE__);
+    mctx->LockDeleteNVP(__FILE__, __LINE__);
+    m_player = mctx->nvp;
+    mctx->UnlockDeleteNVP(__FILE__, __LINE__);
+    tv->ReturnPlayerLock(mctx);
+
+    m_images = new GridEditImages(this, m_player);
+
+}
+
+GridEditCutpoints::~GridEditCutpoints()
+{
+    FUNCTIONLOGGER;
+    gContext->removeListener(this);
+    if (m_images)
+    {
+        delete m_images;
+        m_images = NULL;
+    }
+
+    if (m_tv)
+    {
+        QString message = QString("GRIDEDIT_EXITING");
+        qApp->postEvent(m_tv, new MythEvent(message));
+    }
+
+}
+
+bool GridEditCutpoints::Create()
+{
+    FUNCTIONLOGGER
+
+    if (!LoadWindowFromXML("recordings-ui.xml", "grideditcutpoints", this))
+        return false;
+
+    int i;
+    m_gridimagemain = NULL;
+    for (i = m_gridimages.minIndex(); i < m_gridimages.maxIndex(); i++)
+    {
+        m_gridimages[i] = NULL;
+    }
+
+    usedSubVideoCount=0;
+
+    QSize videoSizeMain, videoSizeSmall;
+
+    m_gridimagemain = dynamic_cast<MythUICutPointImage *> (GetChild("mainvideo"));
+    if (m_gridimagemain)
+        videoSizeMain = m_gridimagemain->GetArea().size();
+    else
+    {
+        VERBOSE(VB_IMPORTANT, "FATAL: Couldn't find grideditcutpoints:mainvideo");
+        return false;
+    }
+
+    // Small version of main frame
+    m_gridimages[0] = dynamic_cast<MythUICutPointImage *> (GetChild("video0"));
+
+    for (i = 1; i < m_gridimages.maxIndex(); i++)
+    {
+        QString p = QString("videop%1").arg(i);
+        QString m = QString("videom%1").arg(i);
+
+        // Minus frame i
+        m_gridimages[-i] = dynamic_cast<MythUICutPointImage *> (GetChild(m));
+        if (m_gridimages[-i])
+        {
+            QSize tmpVideoSizeSmall = m_gridimages[-i]->GetArea().size();
+            if (videoSizeSmall.isValid() && videoSizeSmall != tmpVideoSizeSmall)
+                VERBOSE(VB_IMPORTANT,
+                        QString("Multiple sizes found for edit videos (%1)").arg(m));
+            else
+                videoSizeSmall = tmpVideoSizeSmall;
+            if (i > usedSubVideoCount) usedSubVideoCount=i;
+        }
+
+        m_gridimages[i] = dynamic_cast<MythUICutPointImage *> (GetChild(p));
+        if (m_gridimages[i])
+        {
+            QSize tmpVideoSizeSmall = m_gridimages[i]->GetArea().size();
+            if (videoSizeSmall.isValid() && videoSizeSmall != tmpVideoSizeSmall)
+                VERBOSE(VB_IMPORTANT,
+                        QString("Multiple sizes found for edit videos (%1)").arg(p));
+            else
+                videoSizeSmall = tmpVideoSizeSmall;
+            if (i > usedSubVideoCount) usedSubVideoCount=i;
+        }
+    }
+
+    imageScreenArea = m_gridimages[-usedSubVideoCount]->GetArea();
+    for (i = -usedSubVideoCount; i <= usedSubVideoCount; i++)
+        if (m_gridimages[i])
+            imageScreenArea = imageScreenArea.unite(m_gridimages[i]->GetArea());
+
+    m_slider = dynamic_cast<MythUICutPointBar *> (GetChild("positionbar"));
+    if (!m_slider)
+        VERBOSE(VB_GENERAL, "Missing positionbar for GridEditCutpoints");
+    else
+        m_slider->InitWidth();
+
+    // Get Status boxes
+    {
+        m_framenum    = dynamic_cast<MythUIText *> (GetChild("framenum"));
+        m_time        = dynamic_cast<MythUIText *> (GetChild("time"));
+        m_cutind      = dynamic_cast<MythUIText *> (GetChild("cutind"));
+        m_jumpstyle   = dynamic_cast<MythUIText *> (GetChild("jumpstyle"));
+        m_updatingind = dynamic_cast<MythUIText *> (GetChild("updatingind"));
+    }
+
+    VERBOSE(VB_GENERAL, QString("main = (%1, %2) small = (%3, %4)")
+                       .arg(videoSizeMain.width()).arg(videoSizeMain.height())
+                       .arg(videoSizeSmall.width()).arg(videoSizeSmall.height()));
+
+    slowMotionDirection = 1; // start off play forward
+    slowMotionActive = false;
+    readyForNextFrame = false;
+    slowMotionTimer = NULL;
+    slowMotionTimer = new QTimer(this);
+    QObject::connect(slowMotionTimer, SIGNAL(timeout()),
+                     this,           SLOT(updateSlowMotion()));
+
+    movingCutpoint = false;
+    // Create blank pixmaps...
+
+    m_images->SetVideoInfo(usedSubVideoCount, videoSizeMain, videoSizeSmall);
+
+    m_player->StartEditRecordingGrid();
+
+    LoadInBackground();
+    return true;
+}
+
+void GridEditCutpoints::Load()
+{
+    FUNCTIONLOGGER;
+}
+
+void GridEditCutpoints::Init()
+{
+    FUNCTIONLOGGER;
+
+    // Ensure we're not on keyframe seek
+
+    m_player->SetHalfPageSize(usedSubVideoCount);
+
+    if (m_player->GetSeekAmountPos() == 1)
+        m_player->UpdateSeekAmount(true);
+
+    refreshImages();
+    refreshCutList();
+    updateStats();
+
+    gContext->addListener(this);
+}
+
+void GridEditCutpoints::Close()
+{
+    FUNCTIONLOGGER;
+
+    if (slowMotionTimer)
+        slowMotionTimer->stop();
+
+//    unsetCursor();
+
+    m_player->EndEditRecordingGrid();
+    GetScreenStack()->PopScreen(this, false);
+}
+
+void GridEditCutpoints::displayInitialFrame()
+{
+    refreshImages();
+    refreshCutList();
+    updateStats();
+}
+
+void GridEditCutpoints::updateSlowMotion()
+{
+    if (!slowMotionActive)
+        slowMotionTimer->stop();
+    else if (readyForNextFrame && slowMotionDirection != 0)
+    {
+        readyForNextFrame=false;
+
+        if (slowMotionDirection > 0)
+            EditHandleRight();
+        else if (slowMotionDirection < 0)
+            EditHandleLeft();
+    }
+}
+
+
+void GridEditCutpoints::setSlowMotionSpeed()
+{
+    // slowMotionDirection is max FPS
+
+    if (slowMotionDirection != 0)
+    {
+        int smd = slowMotionDirection;
+        if (smd < 0) smd = -smd;
+        int timeout = 1000 / smd;
+
+        slowMotionTimer->start(timeout);
+    }
+    SetUpdating(true, QString("%1 FPS max").arg(slowMotionDirection));
+}
+
+
+bool GridEditCutpoints::keyPressEvent(QKeyEvent *event)
+{
+    FUNCTIONLOGGER;
+
+    QStringList actions;
+    bool handled = false;
+    handled = GetMythMainWindow()->TranslateKeyPress("TV Editing", event, actions);
+
+    if (handled)
+        return true;
+
+    bool skipMost=false;
+
+    for (unsigned int i = 0; i < actions.size() && !handled; i++)
+    {
+        QString action = actions[i];
+        handled = true;
+
+        VERBOSE(VB_GENERAL, QString("key[%1] = '%2'").arg(i).arg(action));
+
+        if (action == "SELECT" && !skipMost)
+        {
+            if (slowMotionActive)
+                stopSlowMotion();
+            else if (movingCutpoint)
+            {
+                // Move cutpoint
+                m_player->DeleteMark(savedCutpoint);
+                m_player->AddMark(m_images->GetCurrentFrameNumber(), savedCutType);
+                movingCutpoint = false;
+                refreshCutList();
+                refreshImages();
+            }
+            else
+                handleSelect();
+        }
+        else if (action == "PAUSE" && !skipMost)
+            stopSlowMotion();
+        else if (action == "SLOWMO" && !skipMost)
+        {
+            if (movingCutpoint)
+                ShowOkPopup("Slow Motion Unavailable when moving cutpoint");
+            else
+            {
+                if (slowMotionActive)
+                    stopSlowMotion();
+                else
+                    startSlowMotion();
+            }
+        }
+        else if (action == "LEFT" && !skipMost)
+        {
+            if (slowMotionActive)
+            {
+                slowMotionDirection--;
+                setSlowMotionSpeed();
+            }
+            else
+                EditHandleLeft();
+        }
+        else if (action == "RIGHT" && !skipMost)
+        {
+            if (slowMotionActive)
+            {
+                slowMotionDirection++;
+                setSlowMotionSpeed();
+            }
+            else
+                EditHandleRight();
+        }
+        else if (action == "UP" && !skipMost)
+        {
+            m_player->UpdateSeekAmount(true);
+            updateStats();
+        }
+        else if (action == "DOWN" && !skipMost)
+        {
+            if (slowMotionActive && m_player->GetSeekAmountPos() == 2)
+                ShowOkPopup("CutPoint skip Unavailable in Slow Motion");
+            else
+            {
+                m_player->UpdateSeekAmount(false);
+                updateStats();
+            }
+        }
+        else if (action == "CLEARMAP" && !skipMost)
+        {
+            if (movingCutpoint)
+                ShowOkPopup("Clear Cut Map Unavailable when Moving Cutpoint");
+            else if (slowMotionActive)
+                ShowOkPopup("Clear Cut Map Unavailable in Slow Motion");
+            else
+            {
+                SetUpdating(true);
+                m_player->EditHandleClearMap();
+                refreshCutList();
+                updateStats();
+                SetUpdating(false);
+            }
+        }
+        else if (action == "INVERTMAP" && !skipMost)
+        {
+            if (movingCutpoint)
+                ShowOkPopup("Invert Cut Map Unavailable when Moving Cutpoint");
+            else if (slowMotionActive)
+                ShowOkPopup("Invert Cut Map Unavailable in Slow Motion");
+            else
+            {
+                SetUpdating(true);
+                m_player->EditHandleInvertMap();
+                refreshCutList();
+                updateStats();
+                SetUpdating(false);
+            }
+        }
+        else if (action == "LOADCOMMSKIP" && !skipMost)
+        {
+            if (movingCutpoint)
+                ShowOkPopup("Load Comm Skip Map Unavailable when Moving Cutpoint");
+            else if (slowMotionActive)
+                ShowOkPopup("Load Comm Skip Map Unavailable in Slow Motion");
+            else
+            {
+                SetUpdating(true);
+                m_player->EditHandleLoadCommSkip();
+                refreshCutList();
+                updateStats();
+                SetUpdating(false);
+            }
+        }
+        else if (action == "PREVCUT" && !skipMost)
+        {
+            if (slowMotionActive)
+                ShowOkPopup("Prev Cut Unavailable in Slow Motion");
+            else
+                EditHandlePrevCut();
+        }
+        else if (action == "NEXTCUT" && !skipMost)
+        {
+            if (slowMotionActive)
+                ShowOkPopup("Next Cut Unavailable in Slow Motion");
+            else
+                EditHandleNextCut();
+        }
+        else if (action == "BIGJUMPREW" && !skipMost)
+            EditHandleBigJumpRew();
+        else if (action == "BIGJUMPFWD" && !skipMost)
+            EditHandleBigJumpFwd();
+        else if (action == "ESCAPE" && movingCutpoint)
+        {
+            movingCutpoint = false;
+            SetUpdating(false);
+        }
+        else if (action == "ESCAPE" && slowMotionActive)
+            stopSlowMotion();
+        else if (action == "ESCAPE" || action == "TOGGLEEDIT" ||
+                 action == "MENU")
+            Close();
+        else
+            handled = false;
+
+        if (movingCutpoint)
+            SetUpdating(true, "Moving Cutpoint");
+    }
+
+    if (!handled && MythScreenType::keyPressEvent(event))
+        handled = true;
+
+    return handled;
+}
+
+void GridEditCutpoints::customEvent(QEvent *event)
+{
+FUNCTIONLOGGER;
+    bool needupdate = false;
+
+    VERBOSE(VB_GENERAL, QString("Got Event %1").arg(event->type()));
+
+    if (event->type() == DialogCompletionEvent::kEventType)
+    {
+        DialogCompletionEvent *dce = (DialogCompletionEvent*)(event);
+
+        QString resultid   = dce->GetId();
+        QString resulttext = dce->GetResultText();
+        int     buttonnum  = dce->GetResult();
+
+        VERBOSE(VB_GENERAL, QString("Got '%1' '%2' %3")
+                .arg(resultid).arg(resulttext).arg(buttonnum));
+
+        if (resultid == "cutpointexists")
+        {
+            FrameStats fs = m_images->GetMainFrameStats();
+            //  menuPopup->AddButton("Delete cutpoint?");
+            //  menuPopup->AddButton("Move cutpoint?");
+            //  if (fs.cutInd == 2)
+            //      menuPopup->AddButton("Flip directions (Cut After)?");
+            //  else
+            //      menuPopup->AddButton("Flip directions (Cut Before)?");
+
+            if (resulttext == "Delete cutpoint?")
+            {
+                m_player->DeleteMark(fs.frameNumber);
+                needupdate = true;
+            }
+            else if (resulttext == "Move cutpoint?")
+            {
+                // Move cutpoint
+                savedCutpoint = fs.frameNumber;
+                savedCutType = m_player->deleteMap[fs.frameNumber];
+                movingCutpoint = true;
+                // Ensure we're at least at 1 frame motion
+                int i;
+                for (i = 0; m_player->GetSeekAmountPos() < 2 && i < 10; i++)
+                    m_player->UpdateSeekAmount(true);
+                needupdate = true;
+            }
+            else if (resulttext == "Flip directions (Cut After)?" ||
+                     resulttext == "Flip directions (Cut Before)?")
+            {
+                m_player->ReverseMark(fs.frameNumber);
+                needupdate = true;
+            }
+        }
+        else if (resultid == "addcutpoint")
+        {
+            FrameStats fs = m_images->GetMainFrameStats();
+            // menuPopup->AddButton("Delete before this frame");
+            // menuPopup->AddButton("Delete after this frame");
+
+            if (resulttext == "Delete before this frame")
+            {
+                // Delete left
+                m_player->AddMark(fs.frameNumber, MARK_CUT_END);
+                needupdate = true;
+            }
+            else if (resulttext == "Delete after this frame")
+            {
+                // Delete Right
+                m_player->AddMark(fs.frameNumber, MARK_CUT_START);
+                needupdate = true;
+            }
+        }
+
+    } else
+        MythScreenType::customEvent(event);
+
+    if (needupdate)
+    {
+        refreshCutList();
+        refreshImages();
+    }
+}
+
+void GridEditCutpoints::startSlowMotion()
+{
+    if (!slowMotionActive)
+    {
+        slowMotionActive = true;
+        readyForNextFrame = true;
+        slowMotionDirection = 1;
+
+        // force to either 1 frame. 1/2 page or full page motion
+        int i;
+        // move to 1 frame if on cutpoint
+        for (i = 0; m_player->GetSeekAmountPos() < 2 && i < 10; i++)
+            m_player->UpdateSeekAmount(true);
+
+        // move to fullpage if higher
+        for (i = 0; m_player->GetSeekAmountPos() > 4 && i < 10; i++)
+            m_player->UpdateSeekAmount(false);
+
+        setSlowMotionSpeed();
+    }
+}
+
+void GridEditCutpoints::stopSlowMotion()
+{
+    if (slowMotionActive)
+    {
+        slowMotionActive = false;
+        slowMotionTimer->stop();
+        SetUpdating(false);
+    }
+}
+
+
+void GridEditCutpoints::EditHandleLeft(int seektype)
+{
+    long long seekamount = m_player->GetSeekAmount();
+    bool cutpointseek = false;
+
+    if (seektype == -2 || seekamount == -2)
+        cutpointseek = true;
+    else
+    {
+        // seektype == 1 for normal, 10 for bigjump
+        seekamount *= seektype;
+    }
+
+    if (seekamount < 0) // Invalid -- keyframe
+        seekamount = 1;
+
+    m_images->SeekLeft(seekamount, cutpointseek);
+
+    refreshImages();
+    updateStats();
+}
+
+void GridEditCutpoints::EditHandleRight(int seektype)
+{
+    long long seekamount = m_player->GetSeekAmount();
+    bool cutpointseek=false;
+
+    if (seektype == -2 || seekamount == -2)
+        cutpointseek = true;
+    else
+    {
+        // seektype == 1 for normal, 10 for bigjump
+        seekamount *= seektype;
+    }
+
+    if (seekamount < 0) // Invalid -- keyframe
+        seekamount = 1;
+
+    m_images->SeekRight(seekamount, cutpointseek);
+
+    refreshImages();
+    updateStats();
+}
+
+void GridEditCutpoints::handleSelect(void)
+{
+    bool needupdate = false;
+    // add / update cutpoint
+    // -or-
+    // delete / flip / move cutpoint
+
+    // if no cut points on screen
+    //    "Delete Before"
+    //    "Delete After"
+
+    // if on existing cutpoint
+    //    "Delete cutpoint"
+    //    "Flip directions"
+
+    // FIXME
+    // if a cutpoint exists on the screen but not on the current frame
+    //    "Move to current frame"
+    //    "Add new"
+
+    FrameStats fs = m_images->GetMainFrameStats();
+    DeletePointInfo dpi = m_player->GetDeletePointInfo(fs.frameNumber, true);
+
+    MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
+
+    if (fs.cutInd >= 2)
+    {
+        MythDialogBox *menuPopup = new MythDialogBox("Cutpoint Exists", popupStack, "menuPopup");
+        if (menuPopup->Create())
+        {
+            menuPopup->SetReturnEvent(this, "cutpointexists");
+
+            menuPopup->AddButton("Delete cutpoint?", 0, false, true); // Default
+            menuPopup->AddButton("Move cutpoint?");
+            if (fs.cutInd == 2)
+                menuPopup->AddButton("Flip directions (Cut After)?");
+            else
+                menuPopup->AddButton("Flip directions (Cut Before)?");
+
+            popupStack->AddScreen(menuPopup);
+        }
+        else
+        {
+            delete menuPopup;
+        }
+    }
+    else
+    {
+        MythDialogBox *menuPopup = new MythDialogBox("Insert New Cutpoint?", popupStack, "menuPopup");
+        if (menuPopup->Create())
+        {
+            menuPopup->SetReturnEvent(this, "addcutpoint");
+
+            menuPopup->AddButton("Delete before this frame");
+            menuPopup->AddButton("Delete after this frame", 0, false, dpi.cut_after); // might be default
+
+            popupStack->AddScreen(menuPopup);
+        }
+        else
+        {
+            delete menuPopup;
+        }
+    }
+}
+
+void GridEditCutpoints::refreshImages()
+{
+    m_images->refreshImages(m_gridimagemain, m_gridimages, false);
+//    repaint(m_gridimagemain->GetArea());
+//    repaint(imageScreenArea);
+}
+
+void GridEditCutpoints::refreshCutList()
+{
+    m_images->refreshCutList(m_gridimagemain, m_gridimages);
+    refreshSlider();
+}
+
+void GridEditCutpoints::refreshSlider()
+{
+    if (!m_slider)
+        return;
+
+    m_slider->ClearAll();
+
+    const int CUT_LEFT = 0;
+    const int CUT_RIGHT = 1;
+
+    long long startpos = 0;
+    long long endpos = 0;
+
+    int       lastdirection = CUT_LEFT;
+
+    QMap<long long, int> & deleteMap = m_player->deleteMap;
+    QMap<long long, int>::Iterator i = deleteMap.begin();
+    for (; i != deleteMap.end(); ++i)
+    {
+        long long frame = i.key();
+        int direction = *i;
+
+        if (direction == CUT_LEFT)
+        {
+            endpos = frame;
+            m_slider->SetRange(startpos, endpos, m_images->GetMaxFrameNumber());
+
+            startpos = frame;
+            lastdirection = CUT_LEFT;
+        }
+        else if (direction == CUT_RIGHT)
+        {
+            if (lastdirection == CUT_RIGHT)
+            {
+                // continuing within a cutpoint
+                endpos = frame;
+                m_slider->SetRange(startpos, endpos, m_images->GetMaxFrameNumber());
+            }
+
+            startpos = frame;
+            lastdirection = CUT_RIGHT;
+        }
+    }
+
+    if (lastdirection == CUT_RIGHT)
+    {
+        // continuing within a cutpoint
+        endpos = m_images->GetMaxFrameNumber();
+        m_slider->SetRange(startpos, endpos, m_images->GetMaxFrameNumber());
+    }
+}
+
+void GridEditCutpoints::updateStats(bool forcerepaint)
+{
+FUNCTIONLOGGER;
+    int secs, frames, ss, mm, hh;
+
+    FrameStats fs = m_images->GetMainFrameStats();
+
+    secs = (int)(fs.frameNumber / m_player->GetFrameRate());
+    frames = fs.frameNumber - (int)(secs * m_player->GetFrameRate());
+
+    ss = secs;
+    mm = ss / 60;
+    ss %= 60;
+    hh = mm / 60;
+    mm %= 60;
+
+    char timestr[128];
+    sprintf(timestr, "%d:%02d:%02d.%02d", hh, mm, ss, frames);
+
+    char framestr[128];
+    sprintf(framestr, "%lld", fs.frameNumber);
+
+    if (m_time)
+    {
+        m_time->SetText(timestr);
+//        if (forcerepaint)
+//            repaint(m_time->getScreenArea());
+    }
+    if (m_framenum)
+    {
+        m_framenum->SetText(framestr);
+//        if (forcerepaint)
+//            repaint(m_framenum->getScreenArea());
+    }
+    if (m_cutind)
+    {
+        switch (fs.cutInd) {
+            case 0: m_cutind->SetText("");           break;
+            case 1: m_cutind->SetText("Cut");        break;
+            case 2: m_cutind->SetText("Cut Before"); break;
+            case 3: m_cutind->SetText("Cut After");  break;
+        }
+//        if (forcerepaint)
+//            repaint(m_cutind->getScreenArea());
+    }
+
+    // Don't need to force update this
+    if (m_jumpstyle)
+        m_jumpstyle->SetText(m_player->GetSeekAmountText());
+
+    if (m_slider)
+        m_slider->SetCurrentPosition(fs.frameNumber, fs.maxFrameNumber);
+
+}
+
+void GridEditCutpoints::escape()
+{
+    // Make sure we're on the right frame when we go back to
+    // Normal edit mode
+//    unsetCursor();
+//    accept();
+}
+
+void GridEditCutpoints::displayCacheStatus(int level)
+{
+    // 0 - onscreen frames
+    // 1 - precache frames
+    // 2 - fill-out-the buffer frames
+
+    if (!slowMotionActive && !movingCutpoint)
+        switch (level) {
+            case 0: SetUpdating(true, "Displaying"); break;
+            case 1: SetUpdating(true, "Pre-Caching"); break;
+            case 2: SetUpdating(true, "Caching"); break;
+            default: SetUpdating(false);
+        }
+}
+
+void GridEditCutpoints::SetUpdating(bool active, QString text)
+{
+
+    if (m_updatingind)
+    {
+        //VERBOSE(VB_GENERAL, QString("Updating to %1").arg(active));
+        if (active)
+        {
+            m_updatingind->Show();
+            m_updatingind->SetText(text);
+        }
+        else
+            m_updatingind->Hide();
+//        repaint(m_updatingind->getScreenArea());
+    }
+}
+
+void GridEditCutpoints::cacheFramesAreReady()
+{
+    if (slowMotionActive)
+    {
+        readyForNextFrame=true;
+        if (!slowMotionTimer->isActive())
+            slowMotionTimer->start(0);
+    }
+}
+
Index: mythbuild/mythtv/libs/libmythtv/grideditcutpoints.h
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythtv/grideditcutpoints.h
@@ -0,0 +1,107 @@
+// -*- Mode: c++ -*-
+#ifndef GRIDEDITCUTPOINTS_H_
+#define GRIDEDITCUTPOINTS_H_
+
+#include <QObject>
+#include <QEvent>
+
+//#include "libmyth/mythwidgets.h"
+//#include "uitypes.h"
+
+#include "mythscreentype.h"
+#include "grideditimages.h"
+
+
+class QTimer;
+class NuppelVideoPlayer;
+class GridEditImages;
+class MythUICutPointBar;
+class MythUICutPointImage;
+class MythUIText;
+class TV;
+
+class GridEditCutpoints : public MythScreenType
+{
+  Q_OBJECT
+  public:
+    // Use this function to instantiate an GridEditCutpoints instance.
+    static void RunGridEditCutpoints(TV *tv);
+
+    void refreshImages();
+    void cacheFramesAreReady();
+    bool isValid() { return ((usedSubVideoCount > 0) && (m_gridimagemain != NULL)); };
+    void displayCacheStatus(int level);
+
+    GridEditCutpoints(MythScreenStack *parent, QString name, TV *tv);
+   ~GridEditCutpoints();
+
+    bool Create(void);
+    virtual void Load(void);
+    virtual void Init(void);
+    virtual void Close(void);
+
+  protected:
+    void displayInitialFrame();
+
+    void handleSelect();
+
+    void updateStats(bool forcerepaint = false);
+
+    void startSlowMotion();
+    void stopSlowMotion();
+
+  protected slots:
+    void escape();
+    void updateSlowMotion();
+
+    void customEvent(QEvent *event);
+
+  private:
+    bool keyPressEvent(QKeyEvent *e);
+
+    // seektype == -2 - cutpoint seek
+    // seektype ==  1 - normal seek
+    // seektype == 10 - large seek
+    void EditHandleLeft(int seektype = 1);
+    void EditHandleRight(int seektype = 1);
+    void EditHandlePrevCut()    { EditHandleLeft(-2); };
+    void EditHandleNextCut()    { EditHandleRight(-2); };
+    void EditHandleBigJumpRew() { EditHandleLeft(10); };
+    void EditHandleBigJumpFwd() { EditHandleRight(10); };
+    void setSlowMotionSpeed();
+    void refreshCutList();
+    void refreshSlider();
+
+    void SetUpdating(bool active, QString text = "Updating");
+
+    // Private Data
+
+    NuppelVideoPlayer *m_player;
+    TV *m_tv;
+    GridEditImages *m_images;
+
+    int   usedSubVideoCount;
+    myArray<MythUICutPointImage*, MAX_SUB_VIDEOS> m_gridimages;
+    // The main Big image
+    MythUICutPointImage* m_gridimagemain;
+
+    long long savedCutpoint;
+    int       savedCutType;
+    bool      movingCutpoint;
+
+    MythUIText   *m_framenum;
+    MythUIText   *m_time;
+    MythUIText   *m_cutind;
+    MythUIText   *m_jumpstyle;
+    MythUIText   *m_updatingind;
+
+    MythUICutPointBar     *m_slider;
+    QTimer                *slowMotionTimer;
+    int                    slowMotionDirection;
+    bool                   slowMotionActive;
+    bool                   readyForNextFrame;
+
+    QRect                  imageScreenArea;
+};
+
+#endif
Index: mythbuild/mythtv/libs/libmythtv/grideditimages.cpp
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythtv/grideditimages.cpp
@@ -0,0 +1,692 @@
+#include <math.h>
+#include <unistd.h>
+#include <iostream>
+#include <algorithm>
+using namespace std;
+
+#include <QApplication>
+#include <QPainter>
+#include <QFont>
+#include <QKeyEvent>
+#include <QEvent>
+#include <QPixmap>
+#include <QPaintEvent>
+#include <QCursor>
+#include <QImage>
+#include <QLayout>
+#include <QLabel>
+#include <QDateTime>
+#include <QRect>
+
+#include "mythcontext.h"
+#include "mythdbcon.h"
+#include "grideditimages.h"
+#include "grideditcutpoints.h"
+#include "NuppelVideoPlayer.h"
+#include "mythuicutpointimage.h"
+
+#define FRAME_DEBUG 0
+#define CACHE_DEBUG 0
+
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
+
+GridEditImages::GridEditImages(GridEditCutpoints *editor, NuppelVideoPlayer *player)
+{
+    m_editor = editor;
+    m_player = player;
+    usedSubVideoCount = 0;
+
+    lastmovewasright= true;
+
+    int i;
+    for (i = stillFrames.minIndex(); i <= stillFrames.maxIndex(); i++)
+    {
+        stillFrames[i] = NULL;
+        stillFramesBig[i] = NULL;
+        cutFrames[i] = 0;
+    }
+
+    memset(cutFramesCache, 0, sizeof(cutFramesCache));
+    memset(stillFramesCache, 0, sizeof(stillFramesCache));
+    memset(stillFramesBigCache, 0, sizeof(stillFramesBigCache));
+    stillFrameCacheCount = 0;
+    stillFramesCacheBase = 0;
+
+    // Get first frames...
+    stillMainFrameNumber = m_player->GetFramesPlayed();
+    if (stillMainFrameNumber <= 0)
+        stillMainFrameNumber = 1;
+
+    // maxFrameNumber may be overridden if neccessary
+    maxFrameNumber = m_player->GetTotalFrameCount();
+    maxFrameNumberNVP = m_player->GetTotalFrameCount();
+
+    if (stillMainFrameNumber <= 0)
+        stillMainFrameNumber = 1;
+
+    if (stillMainFrameNumber > maxFrameNumber)
+        stillMainFrameNumber = maxFrameNumber;
+
+    getImagesTimer = new QTimer(this);
+    QObject::connect(getImagesTimer, SIGNAL(timeout()),
+                     this,           SLOT(updateAllFrames()));
+
+
+}
+
+GridEditImages::~GridEditImages()
+{
+    emptyCache();
+    clearStillFrames();
+    m_player->EditSeekToFrame(stillMainFrameNumber);
+}
+
+QPixmap *GridEditImages::makeScaledPixmap(const QImage& qim, QSize sz)
+{
+    QPixmap *retval;
+    if (qim.size() == sz)
+    {
+        retval = new QPixmap(sz);
+        QPainter p(retval);
+        p.drawImage(0, 0, qim);
+    }
+    else
+    {
+        retval = new QPixmap(sz);
+        retval->fill(Qt::black);
+        QPainter p(retval);
+
+        int tl_left = 0;
+        int tl_top = 0;
+        if (sz.width() > qim.width())
+            tl_left += (sz.width() - qim.width()) / 2;
+
+        if (sz.height() > qim.height())
+            tl_top += (sz.height() - qim.height()) / 2;
+
+//        VERBOSE(VB_GENERAL, QString("Size mismatch qim(%1, %2) != sz(%3, %4) Shift to (%5, %6)")
+//             .arg(qim.width()).arg(qim.height())
+//             .arg(sz.width()).arg(sz.height())
+//             .arg(tl_left).arg(tl_top));
+
+        p.drawImage(tl_left, tl_top, qim);
+    }
+    return retval;
+}
+
+void GridEditImages::getMainStillFrame()
+{
+    getSpecificFrame(0);
+}
+
+bool GridEditImages::getFrameIndexes(int newlevel)
+{
+    // levels:
+    // 0 - onscreen frames
+    // 1 - precache frames
+    // 2 - fill-out-the buffer frames
+
+    m_editor->displayCacheStatus(newlevel);
+    if (newlevel > 2)
+        return false;
+
+    // This gets the upper and lower indexes of the frames we need to get.
+    // Note these indexes maybe negative
+    // Negative frames are before the main frame
+    // Frame #0 is the main frame
+    // Positive frames are after the main frame
+
+    // Basic initialization
+    // Doesn't go to absolute end because it slows down seeking
+    // when you go 1 frame left and right
+    frameIndexLeft       = stillFrames.minIndex() + 4;
+    frameIndexRight      = stillFrames.maxIndex() - 4;
+    getFutureFramesFirst = false;
+
+    if (newlevel == 0) // Current Display only
+    {
+        frameIndexLeft  = -usedSubVideoCount;
+        frameIndexRight =  usedSubVideoCount;
+    }
+    else
+    {
+        getFutureFramesFirst = lastmovewasright;
+
+        if (newlevel == 1) // PreCache only
+        {
+            // preCacheIndexLeft and Right are indexes for the frames to start and end on
+            frameIndexLeft  = preCacheIndexLeft;
+            frameIndexRight = preCacheIndexRight;
+        }
+    }
+
+    // Make sure we don't fall of the front of the file
+    if (stillMainFrameNumber + frameIndexLeft <= 0)
+        frameIndexLeft = -(stillMainFrameNumber-1);
+
+    // ... or the back of the file
+    if (frameIndexRight > (maxFrameNumber - stillMainFrameNumber))
+        frameIndexRight = (maxFrameNumber - stillMainFrameNumber);
+
+#if CACHE_DEBUG
+    VERBOSE(VB_GENERAL, QString("Getting frames from %1 to %2 for level %3")
+            .arg(frameIndexLeft).arg(frameIndexRight).arg(newlevel));
+#endif
+    return true;
+}
+
+bool GridEditImages::getStillFrames(int maxcount)
+{
+    // returns true if no more frames to get
+
+    // This will fill in all the missing frames in the cache
+
+    long long i;
+    if (getFutureFramesFirst && frameIndexLeft < 0)
+    {
+        // If we're filling out the cache and the last move was to the right
+        // grab future frames first before any past frames
+        for (i = 1; i <= frameIndexRight; i++)
+            if (getSpecificFrame(i))
+                if (--maxcount == 0) return false;
+    }
+
+    // grab all appropriate frames
+
+    for (i = frameIndexLeft; i <= frameIndexRight; i++)
+        if (getSpecificFrame(i))
+            if (--maxcount == 0) return false;
+
+    return true;
+}
+
+bool GridEditImages::getSpecificFrame(long long i)
+{
+    // i is the index within the cache of frames
+
+    // If we're outside of the normal boundaries of the buffer,
+    // see if we've precached this frame
+    if (i < stillFrames.minIndex() || i > stillFrames.maxIndex())
+    {
+        // First extra cached frame
+        if (stillFrameCacheCount == 0)
+            stillFramesCacheBase = stillMainFrameNumber + i;
+
+        int tmpi;
+        for (tmpi = 0; tmpi < stillFrameCacheCount; tmpi++)
+        {
+            long long tmpframe = (stillFramesCacheBase + tmpi);
+            if (tmpframe == stillMainFrameNumber + i)
+                return false;
+        }
+
+        // Check for cache overflow
+        if (stillFrameCacheCount >= MAX_SUB_VIDEOS)
+        {
+            VERBOSE(VB_GENERAL, QString("Cached too many videos. Max = %1").arg(MAX_SUB_VIDEOS));
+            return false;
+        }
+
+        tmpi = stillFrameCacheCount++;
+
+#if CACHE_DEBUG
+        VERBOSE(VB_GENERAL, QString("Caching frame %1, (frm# %2, index %3)")
+                            .arg(tmpi).arg(stillMainFrameNumber + i)
+                            .arg(i));
+#endif
+
+        getFrame(i, cutFramesCache[tmpi], stillFramesCache[tmpi], stillFramesBigCache[tmpi]);
+        return true;
+    }
+    else if (!stillFrames[i])
+    {
+        getFrame(i, cutFrames[i], stillFrames[i], stillFramesBig[i]);
+        return true;
+    }
+
+    return false;
+}
+
+void GridEditImages::getFrame(long long i,
+        int &cutFrame,
+        QPixmap * &stillFrame,
+        QPixmap * &stillFrameBig)
+{
+    // get this frame
+    long long targetFrame = stillMainFrameNumber + i;
+
+    if (!m_player->EditSeekToFrame(targetFrame))
+    {
+        VERBOSE(VB_GENERAL, QString("Error seeking to Frame[%1] (frame # %2)")
+                            .arg(i).arg(targetFrame));
+        checkMaxFrameCount();
+
+        stillFrameBig = new QPixmap(videoSizeMain);
+        stillFrameBig->fill(Qt::gray);
+
+        stillFrame = new QPixmap(videoSizeSmall);
+        stillFrame->fill(Qt::gray);
+    }
+    else
+    {
+        cutFrame = m_player->GetCutStatus(targetFrame);
+        QImage normal, small;
+        m_player->GetScreenGrabsOfCurrentFrame(normal, small);
+
+        stillFrameBig = makeScaledPixmap(normal, videoSizeMain);
+        stillFrame    = makeScaledPixmap(small, videoSizeSmall);
+
+#if FRAME_DEBUG
+        VERBOSE(VB_GENERAL, QString("stillFrames[%1] = %2 (%3)")
+                .arg(i)
+                .arg(targetFrame)
+                .arg(cutFrame));
+#endif
+    }
+}
+
+void GridEditImages::SetVideoInfo(int vcount, QSize sizeMain, QSize sizeSmall)
+{
+    usedSubVideoCount = vcount;
+    videoSizeMain = sizeMain;
+    videoSizeSmall = sizeSmall;
+    SetPreCache(1);
+
+    m_player->SetScreenGrabSizes(videoSizeMain, videoSizeSmall);
+
+    // start to grab the current images
+    getMainStillFrame();
+    startFrameCaching();
+}
+
+void GridEditImages::startFrameCaching()
+{
+    frameCacheLevel=0;
+    getFrameIndexes(frameCacheLevel);
+
+    getImagesTimer->start(0);
+}
+
+void GridEditImages::SetPreCache(long long pccount)
+{
+    preCacheIndexLeft  = pccount - usedSubVideoCount;
+    preCacheIndexRight = pccount + usedSubVideoCount;
+}
+
+void GridEditImages::updateAllFrames()
+{
+    // getStillFrames() returns 'true' on the next call after it's gotten all requested frames
+
+    if (getStillFrames(1))
+    {
+        // If we've pre-cached the next screen of frames, tell the editor about it
+        if (frameCacheLevel == 1)
+            m_editor->cacheFramesAreReady();
+
+        frameCacheLevel++;
+        if (getFrameIndexes(frameCacheLevel))
+            getStillFrames(1);
+        else
+            stopFrameCaching();
+    }
+    m_editor->refreshImages();
+}
+
+void GridEditImages::clearStillFrames()
+{
+    int i;
+    for (i = stillFrames.minIndex(); i <= stillFrames.maxIndex(); i++)
+    {
+        if (stillFrames[i])
+        {
+            delete stillFrames[i];
+            stillFrames[i] = NULL;
+        }
+        if (stillFramesBig[i])
+        {
+            delete stillFramesBig[i];
+            stillFramesBig[i] = NULL;
+        }
+        cutFrames[i] = 0;
+    }
+}
+
+bool GridEditImages::shiftStillFramesLeft(long long offset)
+{
+    if (offset > 2 * stillFrames.maxIndex())
+    {
+        // Dump all cached data and re-get it
+        clearStillFrames();
+    }
+    else if (offset < 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Offset (%1) < 0").arg(offset));
+        // Dump all cached data and re-get it
+        clearStillFrames();
+        offset = 0;
+    }
+    else if (offset != 0)
+    {
+        // Shift backwards in the stream by offset
+
+        // All frames will actually shift to the right.
+        // frame 'n' will become frame 'n+1'
+        // frame stillFrameMinus[1] will become mainframe
+        // frame stillFramePlus[max] will drop off
+
+        // shove extra frames into the excess space above usedSubVideos
+
+        if (offset >= stillMainFrameNumber)
+            offset = (stillMainFrameNumber-1);
+
+        //    printStillFrameStats("Before SL");
+        int i,j;
+        int minIndex = stillFrames.minIndex();
+        int maxIndex = stillFrames.maxIndex();
+        for (i = 0; i < offset; i++)
+        {
+
+            if (stillFrames[maxIndex])
+            {
+                delete stillFrames[maxIndex];
+                delete stillFramesBig[maxIndex];
+            }
+
+            for (j = maxIndex; j > minIndex; j--) {
+                stillFrames[j]    = stillFrames[j-1];
+                stillFramesBig[j] = stillFramesBig[j-1];
+                cutFrames[j]      = cutFrames[j-1];
+            }
+
+             stillFrames[minIndex]    = NULL;
+             stillFramesBig[minIndex] = NULL;
+             cutFrames[minIndex]      = 0;
+        }
+
+        //   printStillFrameStats("After SL");
+
+    }
+
+    stillMainFrameNumber -= offset;
+    if (stillMainFrameNumber < 1)
+        stillMainFrameNumber = 1;
+
+    emptyCache();
+
+    return (stillFramesBig[0] != NULL);
+}
+
+bool GridEditImages::shiftStillFramesRight(long long offset)
+{
+    //VERBOSE(VB_GENERAL, QString("Offset =  %1").arg(offset));
+    if (offset > 2 * stillFrames.maxIndex())
+    {
+        // Dump all cached data and re-get it
+        clearStillFrames();
+    }
+    else if (offset < 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Offset (%1) < 0").arg(offset));
+        // Dump all cached data and re-get it
+        clearStillFrames();
+        offset = 0;
+    }
+    else if (offset != 0)
+    {
+
+        // Shift forwards in the stream by offset
+
+        // All frames will actually shift to the left.
+        // frame 'n' will become frame 'n-1'
+        // frame stillFramePlus[1] will become mainframe
+        // frame stillFrameMinus[max] will drop off
+
+        // shove extra frames into the excess space above usedSubVideos
+
+        if (stillMainFrameNumber + offset > maxFrameNumber)
+        {
+            offset = (maxFrameNumber - stillMainFrameNumber);
+            VERBOSE(VB_GENERAL, QString("new Offset =  %1").arg(offset));
+        }
+        //printStillFrameStats("Before SR");
+
+        int i,j;
+        int minIndex = stillFrames.minIndex();
+        int maxIndex = stillFrames.maxIndex();
+
+        for (i = 0; i < offset; i++)
+        {
+            if (stillFrames[minIndex])
+            {
+                delete stillFrames[minIndex];
+                delete stillFramesBig[minIndex];
+            }
+
+            for (j = minIndex; j < maxIndex; j++) {
+                stillFrames[j]    = stillFrames[j+1];
+                stillFramesBig[j] = stillFramesBig[j+1];
+                cutFrames[j]      = cutFrames[j+1];
+            }
+
+             stillFrames[maxIndex]    = NULL;
+             stillFramesBig[maxIndex] = NULL;
+             cutFrames[maxIndex]      = 0;
+        }
+
+        //printStillFrameStats("After SR");
+
+    }
+    stillMainFrameNumber += offset;
+    if (stillMainFrameNumber > maxFrameNumber )
+        stillMainFrameNumber = maxFrameNumber;
+
+    emptyCache();
+
+    return (stillFramesBig[0] != NULL);
+}
+
+void GridEditImages::emptyCache()
+{
+#if CACHE_DEBUG
+    if (stillFrameCacheCount > 0)
+    {
+        long long minindex = (stillFramesCacheBase - stillMainFrameNumber);
+        long long maxindex = (stillFramesCacheBase+stillFrameCacheCount-1) - stillMainFrameNumber;
+        VERBOSE(VB_GENERAL, QString("emptying %1 frames (%2 - %3) into [%4] - [%5] ")
+                            .arg(stillFrameCacheCount)
+                            .arg(stillFramesCacheBase)
+                            .arg(stillFramesCacheBase+(stillFrameCacheCount-1))
+                            .arg(minindex).arg(maxindex));
+    }
+#endif
+    int i;
+    for (i = 0; i < stillFrameCacheCount; i++)
+    {
+        long long tmpframe = stillFramesCacheBase + i;
+        long long frameIndex = tmpframe - stillMainFrameNumber;
+
+        // frameIndex is the index matching stillFramesCache[i] to stillFrames[frameIndex]
+        // If frameIndex is within the stillFrames range, then use this frame
+        // otherwise delete it
+
+        if (frameIndex >= stillFrames.minIndex() && frameIndex <= stillFrames.maxIndex())
+        {
+            // move cache data into normal arrays.
+            if (stillFrames[frameIndex])
+            {
+                VERBOSE(VB_GENERAL, QString("Frame %1 index %2 already exists")
+                                    .arg(tmpframe).arg(frameIndex));
+                // Can't move it -- the destination exists
+                delete stillFramesCache[i];
+                delete stillFramesBigCache[i];
+            }
+            else
+            {
+                cutFrames[frameIndex] = cutFramesCache[i];
+                stillFrames[frameIndex] = stillFramesCache[i];
+                stillFramesBig[frameIndex] = stillFramesBigCache[i];
+            }
+        }
+        else
+        {
+            delete stillFramesCache[i];
+            delete stillFramesBigCache[i];
+        }
+        cutFramesCache[i]=0;
+        stillFramesCache[i] = NULL;
+        stillFramesBigCache[i] = NULL;
+    }
+    stillFrameCacheCount = 0;
+}
+
+void GridEditImages::printStillFrameStats(QString caption)
+{
+    int i;
+    // Debug info for frame cache
+    QString foundframes= caption + " Found Frames: ";
+
+    for (i = stillFrames.minIndex(); i <= stillFrames.maxIndex(); i++)
+        if (stillFrames[i])
+            foundframes += QString("%1 ").arg(i);
+
+    VERBOSE(VB_GENERAL, foundframes);
+}
+
+void GridEditImages::refreshCutList(MythUICutPointImage* gridimagemain,
+                                    myArray<MythUICutPointImage*, MAX_SUB_VIDEOS> &gridimages)
+{
+    int i;
+    for (i = stillFrames.minIndex(); i <= stillFrames.maxIndex(); i++)
+    {
+        if (stillFrames[i])
+        {
+            cutFrames[i] = m_player->GetCutStatus(stillMainFrameNumber+i);
+            if (gridimages[i])
+                gridimages[i]->setCutStatus(cutFrames[i]);
+        }
+    }
+
+    gridimagemain->setCutStatus(cutFrames[0]);
+}
+
+bool GridEditImages::refreshImages(MythUICutPointImage* gridimagemain,
+                                   myArray<MythUICutPointImage*, MAX_SUB_VIDEOS> &gridimages,
+                                   bool mainFrameOnly)
+{
+FUNCTIONLOGGER;
+//    VERBOSE(VB_GENERAL, "Start");
+    bool alldone = true;
+    if (!stillFramesBig[0])
+        VERBOSE(VB_GENERAL, QString("Null Big Main frame %1").arg(stillMainFrameNumber));
+    gridimagemain->SetPixmap(stillFramesBig[0],
+                             stillMainFrameNumber,
+                             cutFrames[0]);
+
+    if (mainFrameOnly && gridimages[0])
+        gridimages[0]->SetPixmap(stillFrames[0],
+                                 stillMainFrameNumber,
+                                 cutFrames[0]);
+
+    if (!mainFrameOnly)
+    {
+        int i;
+        for (i = -usedSubVideoCount; i <= usedSubVideoCount; i++)
+        {
+            if (stillFrames[i] == NULL)
+                alldone = false;
+            if (gridimages[i])
+                gridimages[i]->SetPixmap(stillFrames[i],
+                                         (stillMainFrameNumber + i),
+                                         cutFrames[i]);
+        }
+    }
+
+//    VERBOSE(VB_GENERAL, "Finish");
+    return alldone;
+}
+
+
+// Back up x frames
+void GridEditImages::SeekLeft(long long seekamount, bool cutpointseek)
+{
+    lastmovewasright = false;
+    stopFrameCaching();
+    m_editor->displayCacheStatus(0);
+
+    if (cutpointseek)
+        seekamount = m_player->CalcCutPointSeek(stillMainFrameNumber, false);
+
+    //VERBOSE(VB_GENERAL, QString("SeekLeft %1, cutpoint = %2").arg(seekamount).arg(cutpointseek));
+
+    if (cutpointseek)
+        SetPreCache(-1);
+    else
+        SetPreCache(-seekamount);
+
+    if (!shiftStillFramesLeft(seekamount))
+    {
+        //VERBOSE(VB_GENERAL, QString("shiftStillFramesLeft(%1) == false")
+        //                           .arg(seekamount));
+        // Need to grab the main frame
+
+        getMainStillFrame();
+    }
+
+    startFrameCaching();
+}
+
+void GridEditImages::SeekRight(long long seekamount, bool cutpointseek)
+{
+    lastmovewasright = true;
+    stopFrameCaching();
+    m_editor->displayCacheStatus(0);
+
+    if (cutpointseek)
+        seekamount = m_player->CalcCutPointSeek(stillMainFrameNumber, true);
+
+    //VERBOSE(VB_GENERAL, QString("SeekRight %1, cutpoint = %2").arg(seekamount).arg(cutpointseek));
+
+    if (cutpointseek)
+        SetPreCache(1);
+    else
+        SetPreCache(seekamount);
+
+    if (!shiftStillFramesRight(seekamount))
+    {
+        //VERBOSE(VB_GENERAL, QString("shiftStillFramesLeft(%1) == false")
+        //                           .arg(seekamount));
+        // Need to grab the main frame
+
+        getMainStillFrame();
+    }
+
+    startFrameCaching();
+}
+
+void GridEditImages::checkMaxFrameCount()
+{
+    long long tfc = m_player->GetTotalFrameCount();
+    if (tfc != maxFrameNumberNVP)
+    {
+       VERBOSE(VB_GENERAL, QString("Updating: tfc %1, mfn %2, mfnNVP %3")
+            .arg(tfc).arg(maxFrameNumber).arg(maxFrameNumberNVP));
+        // Check to see if things changed
+        maxFrameNumber = tfc;
+        maxFrameNumberNVP = tfc;
+    }
+}
+
+FrameStats GridEditImages::GetMainFrameStats()
+{
+    FrameStats result;
+
+    result.frameNumber = stillMainFrameNumber;
+    result.cutInd  = cutFrames[0];
+    result.maxFrameNumber = maxFrameNumber;
+
+    return result;
+}
+
Index: mythbuild/mythtv/libs/libmythtv/grideditimages.h
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythtv/grideditimages.h
@@ -0,0 +1,139 @@
+// -*- Mode: c++ -*-
+#ifndef GRIDEDITIMAGES_H_
+#define GRIDEDITIMAGES_H_
+
+#include <qstring.h>
+
+#include "libmyth/mythwidgets.h"
+
+using namespace std;
+
+class QTimer;
+class NuppelVideoPlayer;
+class GridEditCutpoints;
+class MythUICutPointImage;
+
+#define MAX_SUB_VIDEOS 25
+
+// Simple class to allow array indexing from -MAX_SUB_VIDEOS to +MAX_SUB_VIDEOS
+template<class T, int COUNT> class myArray
+{
+    public:
+        myArray() { memset(_array, 0, sizeof(_array));};
+
+        T& operator[](int i) { return _array[COUNT+i]; };
+        int minIndex() const { return -COUNT; };
+        int maxIndex() const { return  COUNT; };
+
+    private:
+        T _array[2*COUNT+1];
+};
+
+class FrameStats
+{
+    public:
+        long long frameNumber;
+        int cutInd;
+        long long maxFrameNumber;
+};
+
+class MPUBLIC GridEditImages : public QObject
+{
+    Q_OBJECT
+
+    public:
+        GridEditImages(GridEditCutpoints *er, NuppelVideoPlayer *player);
+        ~GridEditImages();
+
+        void refreshCutList(MythUICutPointImage* gridimagemain,
+                            myArray<MythUICutPointImage*, MAX_SUB_VIDEOS> &gridimages);
+
+        // return true if anything changed
+        bool refreshImages(MythUICutPointImage* gridimagemain,
+                           myArray<MythUICutPointImage*, MAX_SUB_VIDEOS> &gridimages,
+                           bool mainFrameOnly);
+
+        void SeekLeft(long long seekamount, bool cutpointseek = false);
+        void SeekRight(long long seekamount, bool cutpointseek = false);
+
+        FrameStats GetMainFrameStats();
+        long long GetCurrentFrameNumber() const { return stillMainFrameNumber; }
+        long long GetMaxFrameNumber() const { return maxFrameNumber; }
+
+        void SetVideoInfo(int vcount, QSize sizeMain, QSize sizeSmall);
+
+    protected slots:
+        void updateAllFrames();
+
+    private:
+        // Private functions
+        void clearStillFrames();
+        void printStillFrameStats(QString caption);
+        void checkMaxFrameCount();
+
+        // 'newlevel' paramter for getStillFrames():
+        //  0 = get on screen Frames only
+        //  1 = get preCache Frames only
+        //  2 = get any necessary frames
+        //  3 = done
+        bool getFrameIndexes(int newlevel);
+
+        bool getStillFrames(int maxcount = 1000);
+        void getMainStillFrame();
+        bool getSpecificFrame(long long frameindex);
+        void getFrame(long long i, int &cutFrame, QPixmap* &stillFrame, QPixmap* &stillFrameBig);
+        void emptyCache();
+
+        // return true if anything changed
+        bool shiftStillFramesLeft(long long offset);
+        bool shiftStillFramesRight(long long offset);
+        void startFrameCaching();
+        void stopFrameCaching() { getImagesTimer->stop(); };
+
+        QPixmap *makeScaledPixmap(const QImage& qim, QSize sz);
+
+        void SetPreCache(long long pccount);
+
+        // Private data
+        // These frames are in the cutlist
+        // 0 == not cut
+        // 1 == cut
+        // 2 == cutpoint (cut left)
+        // 3 == cutpoint (cut right)
+        myArray<int, MAX_SUB_VIDEOS> cutFrames;
+        myArray<QPixmap *, MAX_SUB_VIDEOS> stillFrames;
+        myArray<QPixmap *, MAX_SUB_VIDEOS> stillFramesBig;
+
+        // pre-caching large seek amounts
+        // (i.e. while seeking +5 seconds, these are places to store the to-be-displayed data)
+
+        int       cutFramesCache[MAX_SUB_VIDEOS];
+        QPixmap  *stillFramesCache[MAX_SUB_VIDEOS];
+        QPixmap  *stillFramesBigCache[MAX_SUB_VIDEOS];
+        int       stillFrameCacheCount;
+        long long stillFramesCacheBase; // Frame # for index[0]
+
+        QSize     videoSizeMain;
+        QSize     videoSizeSmall;
+        int       usedSubVideoCount;
+        long long preCacheIndexLeft;
+        long long preCacheIndexRight;
+        long long frameIndexLeft;
+        long long frameIndexRight;
+        bool      lastmovewasright;
+        bool      getFutureFramesFirst;
+        int       frameCacheLevel;
+
+        long long stillMainFrameNumber; // frame number for big still picture
+        long long currentFrameNumberNVP; // frame number the NVP should be on
+        long long maxFrameNumber;       // max frame number override for NVP
+        long long maxFrameNumberNVP;    // Original NVP number
+
+        GridEditCutpoints *m_editor;
+        NuppelVideoPlayer *m_player;
+
+        QTimer            *getImagesTimer;
+
+};
+
+#endif
Index: mythbuild/mythtv/libs/libmythtv/libmythtv.pro
===================================================================
--- mythbuild.orig/mythtv/libs/libmythtv/libmythtv.pro
+++ mythbuild/mythtv/libs/libmythtv/libmythtv.pro
@@ -336,6 +336,8 @@ using_frontend {
     SOURCES += ttfont.cpp
     HEADERS += DetectLetterbox.h
     SOURCES += DetectLetterbox.cpp
+    HEADERS += grideditcutpoints.h      grideditimages.h
+    SOURCES += grideditcutpoints.cpp    grideditimages.cpp
 
     using_mheg {
         # DSMCC stuff
Index: mythbuild/mythtv/libs/libmythtv/tv_play.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythtv/tv_play.cpp
+++ mythbuild/mythtv/libs/libmythtv/tv_play.cpp
@@ -28,6 +28,7 @@ using namespace std;
 #include "remoteencoder.h"
 #include "remoteutil.h"
 #include "tvremoteutil.h"
+#include "grideditcutpoints.h"
 #include "NuppelVideoPlayer.h"
 #include "DetectLetterbox.h"
 #include "programinfo.h"
@@ -72,6 +73,10 @@ using namespace std;
 #define LOC_WARN QString("TV Warning: ")
 #define LOC_ERR  QString("TV Error: ")
 
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
 #define GetPlayer(X,Y) GetPlayerHaveLock(X, Y, __FILE__ , __LINE__)
 #define GetOSDLock(X) GetOSDL(X, __FILE__, __LINE__)
 
@@ -135,6 +140,10 @@ EMBEDRETURNVOIDEPG TV::RunProgramGuidePt
  */
 EMBEDRETURNVOIDFINDER TV::RunProgramFinderPtr = NULL;
 
+/**
+ * \brief function pointer for RunGridEditCutpoints in grideditcutpoints.cpp
+ */
+EMBEDRETURNVOIDGEC TV::RunGridEditCutpointsPtr = NULL;
 
 /**
  * \brief If any cards are configured, return the number.
@@ -451,6 +460,10 @@ void TV::SetFuncPtr(const char *string, 
         RunProgramFinderPtr = (EMBEDRETURNVOIDFINDER)lptr;
     else if (name == "scheduleeditor")
         RunScheduleEditorPtr = (EMBEDRETURNVOIDSCHEDIT)lptr;
+    else if (name == "grideditcutpoints")
+        RunGridEditCutpointsPtr = (EMBEDRETURNVOIDGEC)lptr;
+    else
+        VERBOSE(VB_IMPORTANT, QString("Unknown Function specified: %1").arg(string));
 }
 
 void TV::InitKeys(void)
@@ -726,6 +739,9 @@ void TV::InitKeys(void)
             "Jump back 10x the normal amount"), ",,<");
     REG_KEY("TV Editing", "BIGJUMPFWD",  QT_TRANSLATE_NOOP("MythControls",
             "Jump forward 10x the normal amount"), ">,.");
+    REG_KEY("TV Editing", "EDIT", "Exit out of Edit Mode", "E");
+    REG_KEY("TV Editing", "SLOWMO", "Slow Motion Play", "Ctrl+P");
+    REG_KEY("TV Editing", "PAUSE", "Pause", "P");
 
     /* Teletext keys */
     REG_KEY("Teletext Menu", "NEXTPAGE",    QT_TRANSLATE_NOOP("MythControls",
@@ -871,7 +887,7 @@ TV::TV(void)
       pseudoChangeChanTimerId(0),   speedChangeTimerId(0),
       errorRecoveryTimerId(0),      exitPlayerTimerId(0)
 {
-    VERBOSE(VB_PLAYBACK, LOC + "ctor -- begin");
+    //VERBOSE(VB_PLAYBACK, LOC + "ctor -- begin");
     ctorTime.start();
 
     setObjectName("TV");
@@ -993,7 +1009,7 @@ TV::TV(void)
     player.push_back(new PlayerContext(kPlayerInUseID));
     playerActive = 0;
     playerLock.unlock();
-    VERBOSE(VB_PLAYBACK, LOC + "ctor -- end");
+//    VERBOSE(VB_PLAYBACK, LOC + "ctor -- end");
 }
 
 /** \fn TV::Init(bool)
@@ -3563,6 +3579,11 @@ void TV::ProcessKeypress(PlayerContext *
     if (editmode)
     {
         actx->LockDeleteNVP(__FILE__, __LINE__);
+        if (actx->nvp && actx->nvp->GetHideEdits())
+        {
+            actx->UnlockDeleteNVP(__FILE__, __LINE__);
+            return;
+        }
         if (actx->nvp && !actx->nvp->DoKeypress(e))
             editmode = actx->nvp->GetEditMode();
         actx->UnlockDeleteNVP(__FILE__, __LINE__);
@@ -4541,6 +4562,7 @@ bool TV::ToggleHandleAction(PlayerContex
         if (islivetv)
             StartChannelEditMode(ctx);
         else if (!isDVD)
+            //DoEditRecordingGrid();
             StartProgramEditMode(ctx);
     }
     else
@@ -8022,7 +8044,7 @@ void TV::StopEmbedding(PlayerContext *ct
 
 void TV::DrawUnusedRects(void)
 {
-    VERBOSE(VB_PLAYBACK, LOC + "DrawUnusedRects() -- begin");
+//    VERBOSE(VB_PLAYBACK, LOC + "DrawUnusedRects() -- begin");
 
     PlayerContext *mctx = GetPlayerReadLock(0, __FILE__, __LINE__);
     for (uint i = 0; mctx && (i < player.size()); i++)
@@ -8035,7 +8057,7 @@ void TV::DrawUnusedRects(void)
     }
     ReturnPlayerLock(mctx);
 
-    VERBOSE(VB_PLAYBACK, LOC + "DrawUnusedRects() -- end");
+//    VERBOSE(VB_PLAYBACK, LOC + "DrawUnusedRects() -- end");
 }
 
 vector<bool> TV::DoSetPauseState(PlayerContext *lctx, const vector<bool> &pause)
@@ -8060,6 +8082,48 @@ vector<bool> TV::DoSetPauseState(PlayerC
     return was_paused;
 }
 
+void TV::DoEditRecordingGrid()
+{
+FUNCTIONLOGGER;
+    PlayerContext *actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
+
+    actx->LockPlayingInfo(__FILE__, __LINE__);
+    if (!actx->playingInfo)
+    {
+        VERBOSE(VB_IMPORTANT,
+                LOC_ERR + "no active ctx playingInfo.");
+        actx->UnlockPlayingInfo(__FILE__, __LINE__);
+        ReturnPlayerLock(actx);
+        return;
+    }
+    actx->UnlockPlayingInfo(__FILE__, __LINE__);
+
+    // Resize window to the MythTV GUI size
+    PlayerContext *mctx = GetPlayer(actx,0);
+    mctx->LockDeleteNVP(__FILE__, __LINE__);
+    if (mctx->nvp && mctx->nvp->getVideoOutput())
+        mctx->nvp->getVideoOutput()->ResizeForGui();
+    mctx->UnlockDeleteNVP(__FILE__, __LINE__);
+    ReturnPlayerLock(actx);
+
+    MythMainWindow *mwnd = gContext->GetMainWindow();
+    if (!db_use_gui_size_for_tv || !db_use_fixed_size)
+    {
+        mwnd->setGeometry(saved_gui_bounds.left(), saved_gui_bounds.top(),
+                          saved_gui_bounds.width(), saved_gui_bounds.height());
+        mwnd->setFixedSize(saved_gui_bounds.size());
+    }
+
+    // Actually show the pop-up UI
+
+    RunGridEditCutpointsPtr(this);
+    ignoreKeyPresses = true;
+
+    //we are embedding in a mythui window so show the gui paint window again
+    GetMythMainWindow()->SetDrawEnabled(true);
+    GetMythMainWindow()->GetPaintWindow()->show();
+}
+
 void TV::DoEditSchedule(int editType)
 {
     if ((editType == kScheduleProgramGuide  && !RunProgramGuidePtr) ||
@@ -8191,6 +8255,20 @@ void TV::EditSchedule(const PlayerContex
     qApp->postEvent(gContext->GetMainWindow(), me);
 }
 
+void TV::ShowEditRecordingGrid()
+{
+FUNCTIONLOGGER;
+     // post the request to the main UI thread
+     // it will be caught in eventFilter and processed as CustomEvent
+     // this will create the program guide window (widget)
+     // on the main thread and avoid a deadlock on Win32
+
+     VERBOSE(VB_GENERAL, "Starting Grid Edit");
+     QString message = QString("START_EDIT");
+     MythEvent* me = new MythEvent(message);
+     qApp->postEvent(gContext->GetMainWindow(), me);
+}
+
 void TV::ChangeVolume(PlayerContext *ctx, bool up)
 {
     ctx->LockDeleteNVP(__FILE__, __LINE__);
@@ -8883,6 +8961,7 @@ void TV::customEvent(QEvent *e)
     }
 
     if (message.left(11) == "EPG_EXITING" ||
+        message.left(16) == "GRIDEDIT_EXITING" ||
         message.left(18) == "PROGFINDER_EXITING" ||
         message.left(21) == "VIEWSCHEDULED_EXITING" ||
         message.left(19)   == "PLAYBACKBOX_EXITING" ||
@@ -8939,6 +9018,11 @@ void TV::customEvent(QEvent *e)
 
     }
 
+    if (message.left(10) == "START_EDIT")
+    {
+        DoEditRecordingGrid();
+    }
+
     if (message.left(14) == "COMMFLAG_START")
     {
         QString evchanid = QString::null;
@@ -10172,6 +10256,7 @@ void TV::TreeMenuSelected(OSDListTreeIte
         }
         else if (action == "EDIT")
             StartProgramEditMode(actx);
+            //DoEditRecordingGrid();
         else if (action == "TOGGLEAUTOEXPIRE")
             ToggleAutoExpire(actx);
         else if (action.left(14) == "TOGGLECOMMSKIP")
Index: mythbuild/mythtv/libs/libmythtv/tv_play.h
===================================================================
--- mythbuild.orig/mythtv/libs/libmythtv/tv_play.h
+++ mythbuild/mythtv/libs/libmythtv/tv_play.h
@@ -63,6 +63,7 @@ typedef void (*EMBEDRETURNVOID) (void *,
 typedef void (*EMBEDRETURNVOIDEPG) (uint, const QString &, TV *, bool, bool, int);
 typedef void (*EMBEDRETURNVOIDFINDER) (TV *, bool, bool);
 typedef void (*EMBEDRETURNVOIDSCHEDIT) (const ProgramInfo *, void *);
+typedef void (*EMBEDRETURNVOIDGEC) (TV *);
 
 // Locking order
 //
@@ -166,6 +167,7 @@ class MPUBLIC TV : public QThread
     friend class PlaybackBox;
     friend class GuideGrid;
     friend class TvPlayWindow;
+    friend class GridEditCutpoints;
 
     Q_OBJECT
   public:
@@ -213,6 +215,7 @@ class MPUBLIC TV : public QThread
     void setInPlayList(bool setting) { inPlaylist = setting; }
     void setUnderNetworkControl(bool setting) { underNetworkControl = setting; }
 
+    void ShowEditRecordingGrid();
     void ShowNoRecorderDialog(const PlayerContext*,
                               NoRecorderMsg msgType = kNoRecorders);
     void FinishRecording(int player_idx); ///< Finishes player's recording
@@ -298,6 +301,7 @@ class MPUBLIC TV : public QThread
     void TreeMenuSelected(OSDListTreeItemSelectedEvent *e);
 
     void DoEditSchedule(int editType = kScheduleProgramGuide);
+    void DoEditRecordingGrid();
 
     virtual void run(void);
     void TVEventThreadChecks(void);
@@ -312,6 +316,7 @@ class MPUBLIC TV : public QThread
     static EMBEDRETURNVOIDEPG RunProgramGuidePtr;
     static EMBEDRETURNVOIDFINDER RunProgramFinderPtr;
     static EMBEDRETURNVOIDSCHEDIT RunScheduleEditorPtr;
+    static EMBEDRETURNVOIDGEC RunGridEditCutpointsPtr;
 
   private:
     void SetActive(PlayerContext *lctx, int index, bool osd_msg);
Index: mythbuild/mythtv/themes/MythCenter-wide/recordings-ui.xml
===================================================================
--- mythbuild.orig/mythtv/themes/MythCenter-wide/recordings-ui.xml
+++ mythbuild/mythtv/themes/MythCenter-wide/recordings-ui.xml
@@ -1085,4 +1085,109 @@
         </buttonlist>
     </window>
 
+    <window name="grideditcutpoints">
+        <font name="white_medium" face="DejaVu Sans">
+           <color>#FFFFFF</color>
+           <size>20</size>
+        </font>
+
+        <font name="yellow_medium" face="DejaVu Sans">
+            <color>#FFFF00</color>
+            <size>20</size>
+        </font>
+
+			<area>0,0,1280,720</area>
+
+                        <textarea name="timecap" align="right">
+                                <area>0,350,120,40</area>
+                                <font>white_medium</font>
+				<value>Time:</value>
+                        </textarea>
+
+                        <textarea name="time" align="left">
+                                <area>125,350,195,40</area>
+                                <font>white_medium</font>
+                        </textarea>
+
+                        <textarea name="framenumcap" align="right">
+                                <area>0,400,120,40</area>
+                                <font>white_medium</font>
+				<value>Frame:</value>
+                        </textarea>
+
+                        <textarea name="framenum" align="left" >
+                                <area>125,400,195,40</area>
+                                <font>white_medium</font>
+                        </textarea>
+
+                        <textarea name="cutind" align="left" >
+                                <area>120,450,195,40</area>
+                                <font>white_medium</font>
+                        </textarea>
+
+                        <textarea name="updatingind" align="center" >
+                                <area>20,550,210,40</area>
+                                <font>yellow_medium</font>
+				<value>Updating</value>
+                        </textarea>
+
+                        <textarea name="jumpstylecap" align="right" >
+                                <area>0,500,120,40</area>
+                                <font>white_medium</font>
+				<value>Skip:</value>
+                        </textarea>
+
+                        <textarea name="jumpstyle" align="left" >
+                                <area>125,500,195,40</area>
+                                <font>white_medium</font>
+                        </textarea>
+
+                        <cutpointbar name="positionbar">
+                               <area>338,554,908,48</area>
+                               <fillcolor>#202020</fillcolor>
+                               <cutcolor>#7f0000</cutcolor>
+                               <incutcolor>#7f7f00</incutcolor>
+                               <cutpointcolor>#FFFF00</cutpointcolor>
+                               <positioncolor>#FFFFFF</positioncolor>
+                        </cutpointbar>
+
+
+			<cutpointimage name="mainvideo">
+				<area>328,24,928,522</area>
+                                <outlinecolor>#202020</outlinecolor>
+                                <cutcolor>#7f0000</cutcolor>
+                                <cutpointcolor>#afaf00</cutpointcolor>
+			</cutpointimage>
+
+			<cutpointimage name="videom3" from="mainvideo">
+				<area>6,614,176,99</area>
+			</cutpointimage>
+
+			<cutpointimage name="videom2" from="mainvideo">
+				<area>188,614,176,99</area>
+			</cutpointimage>
+
+			<cutpointimage name="videom1" from="mainvideo">
+				<area>370,614,176,99</area>
+			</cutpointimage>
+
+			<cutpointimage name="video0" from="mainvideo">
+				<area>552,614,176,99</area>
+                                <highlightcolor>#ffffff</highlightcolor>
+			</cutpointimage>
+
+			<cutpointimage name="videop1" from="mainvideo">
+				<area>734,614,176,99</area>
+			</cutpointimage>
+
+			<cutpointimage name="videop2" from="mainvideo">
+				<area>916,614,176,99</area>
+			</cutpointimage>
+
+			<cutpointimage name="videop3" from="mainvideo">
+				<area>1098,614,176,99</area>
+			</cutpointimage>
+
+        </window>
+
 </mythuitheme>
Index: mythbuild/mythtv/programs/mythfrontend/main.cpp
===================================================================
--- mythbuild.orig/mythtv/programs/mythfrontend/main.cpp
+++ mythbuild/mythtv/programs/mythfrontend/main.cpp
@@ -69,6 +69,8 @@ using namespace std;
 #include "mythdb.h"
 #include "backendconnectionmanager.h"
 
+#include "grideditcutpoints.h"
+
 static ExitPrompter   *exitPopup = NULL;
 static MythThemedMenu *menu;
 static MythThemeBase  *themeBase = NULL;
@@ -965,6 +967,7 @@ void InitJumpPoints(void)
     TV::SetFuncPtr("programguide", (void *)GuideGrid::RunProgramGuide);
     TV::SetFuncPtr("programfinder", (void *)RunProgramFinder);
     TV::SetFuncPtr("scheduleeditor", (void *)ScheduleEditor::RunScheduleEditor);
+    TV::SetFuncPtr("grideditcutpoints", (void *)GridEditCutpoints::RunGridEditCutpoints);
 }
 
 
Index: mythbuild/mythtv/programs/mythfrontend/guidegrid.cpp
===================================================================
--- mythbuild.orig/mythtv/programs/mythfrontend/guidegrid.cpp
+++ mythbuild/mythtv/programs/mythfrontend/guidegrid.cpp
@@ -208,6 +208,10 @@ void GuideGrid::RunProgramGuide(uint cha
         mainStack->AddScreen(gg, (player == NULL));
     else
         delete gg;
+
+    QString depth = mainStack->GetLocation(true);
+
+    VERBOSE(VB_GENERAL, QString("Screens: %1").arg(depth));
 }
 
 GuideGrid::GuideGrid(MythScreenStack *parent,
Index: mythbuild/mythtv/libs/libmythui/mythscreenstack.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/mythscreenstack.cpp
+++ mythbuild/mythtv/libs/libmythui/mythscreenstack.cpp
@@ -4,6 +4,8 @@
 #include "mythpainter.h"
 #include "mythevent.h"
 
+#include "mythverbose.h"
+
 #include <cassert>
 
 #include <QCoreApplication>
@@ -75,6 +77,10 @@ void MythScreenStack::AddScreen(MythScre
     screen->aboutToShow();
 
     m_topScreen = screen;
+
+//    reinterpret_cast<MythMainWindow *>(parent())->ShowAllStacksBut(objectName());
+//    QString loc = GetLocation(true);
+//    VERBOSE(VB_GENERAL, QString("Location ('%1') = %2").arg(objectName()).arg(loc));
 }
 
 void MythScreenStack::PopScreen(bool allowFade,
@@ -89,6 +95,8 @@ void MythScreenStack::PopScreen(MythScre
     if (!screen || screen->IsDeleting())
         return;
 
+    QString popname = screen->objectName();
+
     screen->aboutToHide();
 
     if (m_Children.isEmpty())
@@ -160,6 +168,10 @@ void MythScreenStack::PopScreen(MythScre
         if (mainscreen)
             mainscreen->SetRedraw();
     }
+
+//    QString loc = GetLocation(true);
+//    VERBOSE(VB_GENERAL, QString("Location ('%1') = %2 popped %3").arg(objectName()).arg(loc).arg(popname));
+//    reinterpret_cast<MythMainWindow *>(parent())->ShowAllStacksBut(objectName());
 }
 
 MythScreenType *MythScreenStack::GetTopScreen(void) const
Index: mythbuild/mythtv/libs/libmythdb/mythobservable.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythdb/mythobservable.cpp
+++ mythbuild/mythtv/libs/libmythdb/mythobservable.cpp
@@ -3,6 +3,7 @@
 #include <QMutex>
 
 #include "mythobservable.h"
+#include "mythverbose.h"
 
 /** \class MythObservable
  *  \brief Superclass for making an object have a set of listeners
@@ -43,9 +44,11 @@ void MythObservable::addListener(QObject
 {
     if (listener)
     {
+        VERBOSE(VB_GENERAL, QString("Adding '%1'").arg(listener->objectName()));
         QMutexLocker locker(m_lock);
         m_listeners.insert(listener);
     }
+//    ShowListeners();
 }
 
 
@@ -60,10 +63,31 @@ void MythObservable::removeListener(QObj
 {
     if (listener)
     {
+        VERBOSE(VB_GENERAL, QString("Removing '%1'").arg(listener->objectName()));
         QMutexLocker locker(m_lock);
         m_listeners.remove(listener);
         QCoreApplication::removePostedEvents(listener);
     }
+//    ShowListeners();
+}
+
+void MythObservable::ShowListeners()
+{
+    QString output="";
+    {
+        QMutexLocker locker(m_lock);
+
+        QSet<QObject*>::const_iterator it = m_listeners.begin();
+        for (; it != m_listeners.end() ; ++it)
+        {
+            QString oname = (*it)->objectName();
+            if (oname != "")
+                output = output + oname + ", ";
+            else
+                output = output + "<none>, ";
+        }
+    }
+    VERBOSE(VB_GENERAL, QString("Listeners = '%1'").arg(output));
 }
 
 /** \brief Dispatch an event to all listeners
@@ -76,6 +100,7 @@ void MythObservable::removeListener(QObj
  */
 void MythObservable::dispatch(const MythEvent &event)
 {
+//    VERBOSE(VB_GENERAL, QString("Dispatching '%1'").arg(event.Message()));
     QMutexLocker locker(m_lock);
 
     QSet<QObject*>::const_iterator it = m_listeners.begin();
@@ -92,6 +117,7 @@ void MythObservable::dispatch(const Myth
  */
 void MythObservable::dispatchNow(const MythEvent &event)
 {
+//    VERBOSE(VB_GENERAL, QString("DispatchingNow '%1'").arg(event.Message()));
     QMutexLocker locker(m_lock);
 
     QSet<QObject*>::const_iterator it = m_listeners.begin();
Index: mythbuild/mythtv/libs/libmythdb/mythobservable.h
===================================================================
--- mythbuild.orig/mythtv/libs/libmythdb/mythobservable.h
+++ mythbuild/mythtv/libs/libmythdb/mythobservable.h
@@ -20,6 +20,8 @@ class MPUBLIC MythObservable
     void dispatch(const MythEvent &event);
     void dispatchNow(const MythEvent &event) MDEPRECATED;
 
+    void ShowListeners();
+
   private:
     QMutex         *m_lock;
     QSet<QObject*>  m_listeners;
Index: mythbuild/mythtv/libs/libmythui/mythmainwindow.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/mythmainwindow.cpp
+++ mythbuild/mythtv/libs/libmythui/mythmainwindow.cpp
@@ -581,6 +581,19 @@ MythScreenStack *MythMainWindow::GetStac
     return NULL;
 }
 
+void MythMainWindow::ShowAllStacksBut(QString name)
+{
+    QVector<MythScreenStack *>::Iterator it;
+    for (it = d->stackList.begin(); it != d->stackList.end(); ++it)
+    {
+        if ((*it)->objectName() != name)
+        {
+            QString loc = (*it)->GetLocation(true);
+            VERBOSE(VB_GENERAL, QString("Location ('%1' != '%2') = %3").arg((*it)->objectName()).arg(name).arg(loc));
+        }
+    }
+}
+
 void MythMainWindow::RegisterSystemEventHandler(QObject *eventHandler)
 {
     d->sysEventHandler = eventHandler;
Index: mythbuild/mythtv/libs/libmythui/mythmainwindow.h
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/mythmainwindow.h
+++ mythbuild/mythtv/libs/libmythui/mythmainwindow.h
@@ -110,6 +110,8 @@ class MPUBLIC MythMainWindow : public QW
     void SetDrawEnabled(bool enable);
     void SetEffectsEnabled(bool enable);
 
+    void ShowAllStacksBut(QString name);
+
   public slots:
     void mouseTimeout();
 
Index: mythbuild/mythtv/libs/libmythui/mythuishape.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/mythuishape.cpp
+++ mythbuild/mythtv/libs/libmythui/mythuishape.cpp
@@ -18,6 +18,10 @@ using namespace std;
 #include "mythimage.h"
 #include "mythmainwindow.h"
 
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
 MythUIShape::MythUIShape(MythUIType *parent, const QString &name)
           : MythUIType(parent, name)
 {
@@ -26,6 +30,7 @@ MythUIShape::MythUIShape(MythUIType *par
     m_fillBrush = QBrush(Qt::NoBrush);
     m_linePen = QPen(Qt::NoPen);
     m_cornerRadius = 10;
+    m_pm = NULL;
 }
 
 MythUIShape::~MythUIShape()
@@ -51,6 +56,12 @@ void MythUIShape::Reset()
 void MythUIShape::DrawSelf(MythPainter *p, int xoffset, int yoffset,
                           int alphaMod, QRect clipRect)
 {
+//FUNCTIONLOGGER;
+//    if (m_image)
+//        VERBOSE(VB_GENERAL, QString("Has Image 1 (null? %1), has pixmap %2").arg(m_image->isNull()).arg(m_pm != NULL));
+//    else
+//        VERBOSE(VB_GENERAL, QString("Has Image 0, has pixmap %1").arg(m_pm != NULL));
+
     QRect area = GetArea();
     area.translate(xoffset, yoffset);
 
@@ -62,13 +73,35 @@ void MythUIShape::DrawSelf(MythPainter *
             DrawRoundRect(area, m_cornerRadius, m_fillBrush, m_linePen);
     }
 
-    if (m_image)
+    if (m_image) {
+//        VERBOSE(VB_GENERAL, QString("Drawing (%1, %2, xx, %3)").arg(area.x()).arg(area.y()).arg(alphaMod));
         p->DrawImage(area.x(), area.y(), m_image, alphaMod);
+    }
+}
+
+void MythUIShape::SetPixmap(QPixmap * pm)
+{
+//FUNCTIONLOGGER;
+    m_pm = pm;
+
+    if (m_image)
+    {
+        m_image->DownRef();
+        m_image = NULL;
+    }
+
+    m_image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
+    m_image->UpRef();
+    if (m_pm)
+        m_image->Assign(*m_pm);
+
+    SetRedraw();
 }
 
 void MythUIShape::DrawRect(const QRect &area,const QBrush &fillBrush,
                            const QPen &linePen)
 {
+//FUNCTIONLOGGER;
     if (m_image)
     {
         m_image->DownRef();
@@ -93,12 +126,19 @@ void MythUIShape::DrawRect(const QRect &
 
     m_image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
     m_image->UpRef();
-    m_image->Assign(image);
+    if (m_pm) {
+//        VERBOSE(VB_GENERAL, "m_pm !=  NULL");
+        m_image->Assign(*m_pm);
+    } else {
+//        VERBOSE(VB_GENERAL, "m_pm ==  NULL");
+        m_image->Assign(image);
+    }
 }
 
 void MythUIShape::DrawRoundRect(const QRect &area, int radius,
                                 const QBrush &fillBrush, const QPen &linePen)
 {
+//FUNCTIONLOGGER;
     if (m_image)
     {
         m_image->DownRef();
@@ -129,7 +169,14 @@ void MythUIShape::DrawRoundRect(const QR
 
     m_image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
     m_image->UpRef();
-    m_image->Assign(image);
+
+    if (m_pm) {
+//        VERBOSE(VB_GENERAL, "m_pm !=  NULL");
+        m_image->Assign(*m_pm);
+    } else {
+//        VERBOSE(VB_GENERAL, "m_pm ==  NULL");
+        m_image->Assign(image);
+    }
 }
 
 bool MythUIShape::ParseElement(
Index: mythbuild/mythtv/libs/libmythui/mythuishape.h
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/mythuishape.h
+++ mythbuild/mythtv/libs/libmythui/mythuishape.h
@@ -20,6 +20,8 @@ class MPUBLIC MythUIShape : public MythU
 
     void Reset(void);
 
+    void SetPixmap(QPixmap * pm);
+
   protected:
     virtual void DrawSelf(MythPainter *p, int xoffset, int yoffset,
                           int alphaMod, QRect clipRect);
@@ -41,6 +43,8 @@ class MPUBLIC MythUIShape : public MythU
     QBrush         m_fillBrush;
     QPen           m_linePen;
     int            m_cornerRadius;
+
+    QPixmap        *m_pm;
 };
 
 #endif
Index: mythbuild/mythtv/libs/libmythui/libmythui.pro
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/libmythui.pro
+++ mythbuild/mythtv/libs/libmythui/libmythui.pro
@@ -28,6 +28,7 @@ HEADERS += themeinfo.h mythxdisplay.h Di
 HEADERS += mythgenerictree.h mythuibuttontree.h mythuiutils.h
 HEADERS += mythvirtualkeyboard.h mythuishape.h mythuiguidegrid.h
 HEADERS += mythrender_base.h mythfontmanager.h
+HEADERS += mythuicutpointbar.h mythuicutpointimage.h
 
 SOURCES  = mythmainwindow.cpp mythpainter.cpp mythimage.cpp mythrect.cpp
 SOURCES += myththemebase.cpp
@@ -44,6 +45,7 @@ SOURCES += themeinfo.cpp mythxdisplay.cp
 SOURCES += mythgenerictree.cpp mythuibuttontree.cpp mythuiutils.cpp
 SOURCES += mythvirtualkeyboard.cpp mythuishape.cpp mythuiguidegrid.cpp
 SOURCES += mythfontmanager.cpp
+SOURCES += mythuicutpointbar.cpp mythuicutpointimage.cpp
 
 inc.path = $${PREFIX}/include/mythtv/libmythui/
 
@@ -58,6 +60,7 @@ inc.files += mythuispinbox.h mythuicheck
 inc.files += mythuiprogressbar.h mythuiwebbrowser.h mythuiutils.h
 inc.files += mythsystem.h x11colors.h mythgenerictree.h mythuibuttontree.h
 inc.files += mythvirtualkeyboard.h mythuishape.h mythuiguidegrid.h
+inc.files += mythuicutpointbar.h mythuicutpointimage.h
 
 INSTALLS += inc
 
Index: mythbuild/mythtv/libs/libmythui/mythuicutpointbar.cpp
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythui/mythuicutpointbar.cpp
@@ -0,0 +1,404 @@
+
+// Own Header
+#include "mythuicutpointbar.h"
+
+// QT
+#include <QCoreApplication>
+#include <QDomDocument>
+
+// MythDB
+#include "mythverbose.h"
+
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
+// MythUI
+
+
+MythUICutPointBar::MythUICutPointBar(MythUIType *parent, const QString &name)
+                  : MythUIType(parent, name),
+                    m_layout(LayoutHorizontal), m_effect(EffectReveal),
+                    m_total(0),                 m_start(0),
+                    m_current(0)
+{
+    m_drawMap = NULL;
+    m_position=0;
+}
+
+void MythUICutPointBar::InitWidth()
+{
+    if (m_drawMap)
+        delete [] m_drawMap;
+
+    m_drawWidth = GetArea().width();
+    m_drawMap = new unsigned char[m_drawWidth];
+    for (int i = 0; i < m_drawWidth; i++)
+        m_drawMap[i] = 0;
+}
+
+void MythUICutPointBar::Reset()
+{
+    m_total = m_start = m_current = 0;
+    CalculatePosition();
+    MythUIType::Reset();
+}
+
+void MythUICutPointBar::SetRange(
+        long long fstart, long long fend, long long fcount)
+{
+    if (fcount <= 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Invalid frame count: %1")
+                .arg(fcount));
+        return;
+    }
+    if (fstart < 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Invalid starting frame: %1")
+                .arg(fstart));
+        return;
+    }
+    if (fend < 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Invalid ending frame: %1")
+                .arg(fend));
+        return;
+    }
+
+    if (fstart > fcount) fstart = fcount;
+    if (fend > fcount) fend = fcount;
+
+    int start = (int)((1.0 * fstart * m_drawWidth) / fcount);
+    int end   = (int)((1.0 * fend   * m_drawWidth) / fcount);
+
+    if (start < 0)
+        start = 0;
+    if (start >= m_drawWidth)
+        start = m_drawWidth - 1;
+    if (end < 0)
+        end = 0;
+    if (end >= m_drawWidth)
+        end = m_drawWidth - 1;
+
+    if (end < start)
+    {
+        int tmp = start;
+        start = end;
+        end = tmp;
+    }
+
+    for (int i = start; i < end; i++)
+        if (m_drawMap[i] < 1)
+            m_drawMap[i] = 1;
+
+    // Mark endpoints
+
+    m_drawMap[start] = 2;
+    m_drawMap[end]   = 2;
+
+    VERBOSE(VB_GENERAL, QString("Range = %1 - %2 (%3 - %4)")
+            .arg(start).arg(end)
+            .arg(fstart).arg(fend));
+    SetRedraw();
+}
+
+void MythUICutPointBar::SetCurrentPosition(
+        long long fposition, long long fcount)
+{
+    if (fcount <= 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Invalid frame count: %1")
+                .arg(fcount));
+        return;
+    }
+
+    if (fposition < 0)
+    {
+        VERBOSE(VB_IMPORTANT, QString("Invalid position frame: %1")
+                .arg(fposition));
+        return;
+    }
+
+    if (fposition > fcount) fposition = fcount;
+
+    int new_position = (int)(1.0 * fposition * m_drawWidth) / fcount;
+
+    if (new_position < 0)
+        new_position = 0;
+    if (new_position >= m_drawWidth)
+       new_position = m_drawWidth - 1;
+
+    if (new_position != m_position)
+    {
+        m_position = new_position;
+        SetRedraw();
+    }
+}
+
+
+
+bool MythUICutPointBar::ParseElement(
+    const QString &filename, QDomElement &element, bool showWarnings)
+{
+VERBOSE(VB_GENERAL, QString("Got %1").arg(element.tagName()));
+
+    if (element.tagName() == "fillcolor")
+    {
+        QColor color = getFirstText(element);
+        setFillColor(color);
+    }
+    else if (element.tagName() == "cutcolor")
+    {
+        QColor color = getFirstText(element);
+        setCutColor(color);
+    }
+    else if (element.tagName() == "incutcolor")
+    {
+        QColor color = getFirstText(element);
+        setInCutColor(color);
+    }
+    else if (element.tagName() == "incutcolor")
+    {
+        QColor color = getFirstText(element);
+        setInCutColor(color);
+    }
+    else if (element.tagName() == "cutpointcolor")
+    {
+        QColor color = getFirstText(element);
+        setCutPointColor(color);
+    }
+    else if (element.tagName() == "positioncolor")
+    {
+        QColor color = getFirstText(element);
+        setPositionColor(color);
+    }
+    else
+    {
+        return MythUIType::ParseElement(filename, element, showWarnings);
+    }
+
+    return true;
+}
+
+void MythUICutPointBar::SetStart(int value)
+{
+    m_start = value;
+    CalculatePosition();
+}
+
+void MythUICutPointBar::SetUsed(int value)
+{
+    if (value < m_start)
+        value = m_start;
+
+    if (value > m_total)
+        value = m_total;
+
+    m_current = value;
+    CalculatePosition();
+}
+
+void MythUICutPointBar::SetTotal(int value)
+{
+    m_total = value;
+    CalculatePosition();
+}
+
+void MythUICutPointBar::CalculatePosition(void)
+{
+    MythUIImage *progressImage = dynamic_cast<MythUIImage *>
+                                         (GetChild("progressimage"));
+
+    if (!progressImage)
+    {
+        VERBOSE(VB_IMPORTANT, "Progress image doesn't exist");
+        return;
+    }
+
+    progressImage->SetVisible(false);
+
+    int total = m_total-m_start;
+    int current = m_current-m_start;
+    float percentage = 0.0;
+
+    if (total <= 0 || current <= 0 || current > total)
+        return;
+
+    percentage = (float)current / (float)total;
+    progressImage->SetVisible(true);
+
+    QRect fillArea = progressImage->GetArea();
+
+    int height = fillArea.height();
+    int width = fillArea.width();
+    int x = fillArea.x();
+    int y = fillArea.y();
+
+    switch (m_effect)
+    {
+        case EffectReveal :
+            if (m_layout == LayoutHorizontal)
+            {
+                width = (int)((float)fillArea.width() * percentage);
+            }
+            else
+            {
+                height = (int)((float)fillArea.height() * percentage);
+            }
+        break;
+        case EffectSlide :
+            if (m_layout == LayoutHorizontal)
+            {
+                int newwidth = (int)((float)fillArea.width() * percentage);
+                x = width - newwidth;
+                width = newwidth;
+            }
+            else
+            {
+                int newheight = (int)((float)fillArea.height() * percentage);
+                y = height - newheight;
+                height = newheight;
+            }
+        break;
+        case EffectAnimate :
+            // Not implemented yet
+        break;
+    }
+
+    if (width <= 0)
+        width = 1;
+
+    if (height <= 0)
+        height = 1;
+
+//    progressImage->SetCropRect(x,y,width,height);
+    SetRedraw();
+}
+
+void MythUICutPointBar::CopyFrom(MythUIType *base)
+{
+    MythUICutPointBar *progressbar = dynamic_cast<MythUICutPointBar *>(base);
+    if (!progressbar)
+        return;
+
+    m_layout = progressbar->m_layout;
+    m_effect = progressbar->m_effect;
+
+    m_total = progressbar->m_total;
+    m_start = progressbar->m_start;
+    m_current = progressbar->m_current;
+
+    MythUIType::CopyFrom(base);
+}
+
+void MythUICutPointBar::CreateCopy(MythUIType *parent)
+{
+    MythUICutPointBar *progressbar = new MythUICutPointBar(parent, objectName());
+    progressbar->CopyFrom(this);
+}
+
+void MythUICutPointBar::DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod,
+              QRect clipRegion)
+{
+FUNCTIONLOGGER;
+    QRect fillArea = GetArea();
+
+    int height = fillArea.height();
+    int width = fillArea.width();
+    int x = fillArea.x();
+    int y = fillArea.y();
+
+    VERBOSE(VB_GENERAL, QString(" area = (%1, %2, %3, %4)")
+		.arg(x).arg(y).arg(width).arg(height));
+
+    p->DrawRect(fillArea,
+                true, colorSet[0],
+                false, 0, colorSet[0]);
+
+    if (m_drawMap)
+    {
+        int i = 0;
+
+        do {
+            int start = 0;
+            int end = 0;
+
+            while (i < m_drawWidth && m_drawMap[i] == 0) i++;
+            if (i == m_drawWidth) break;
+            start = i;
+
+            i++;
+
+            while (i < m_drawWidth && m_drawMap[i] == 1) i++;
+            end = i;
+            if (end == m_drawWidth) end--;
+
+            // If the next map value is not a normal internal cutpoint
+            // increment i so we handle it properly
+            if (end+1 < m_drawWidth && m_drawMap[end+1] != 1)
+                i++;
+
+            // start == starting point
+            //   end == endingpoint
+            {
+                QRect r = fillArea;
+                r.setLeft(r.left() + start);
+                r.setWidth(end - start);
+
+//            VERBOSE(VB_GENERAL, QString("Cut from (%1, %2) - (%3, %4)")
+//                    .arg(r.left()).arg(r.top())
+//                    .arg(r.right()).arg(r.bottom()));
+
+//            VERBOSE(VB_GENERAL, QString("start = %1, m_position = %2, end = %3")
+//                    .arg(start)
+//                    .arg(m_position)
+//                    .arg(end));
+
+                if (start <= m_position && m_position <= end)
+                {
+                    p->DrawRect(r,
+                        true, colorSet[4],
+                        true, 2, colorSet[4]);
+                }
+                else
+                {
+                    p->DrawRect(r,
+                        true, colorSet[1],
+                        true, 2, colorSet[4]);
+                }
+
+                if (m_drawMap[start] == 2)
+                {
+                    QRect markArea(r.topLeft(), r.bottomLeft());
+                    p->DrawRect(markArea,
+                        true, colorSet[2],
+                        true, 2, colorSet[2]);
+                }
+                if (m_drawMap[end] == 2)
+                {
+                    QRect markArea(r.topRight(), r.bottomRight());
+                    p->DrawRect(markArea,
+                        true, colorSet[2],
+                        true, 2, colorSet[2]);
+                }
+            }
+        } while (i < m_drawWidth);
+    }
+    // Draw Current Position Mark
+
+    QPoint ptop(fillArea.left() + m_position, fillArea.top());
+    QPoint pbot(fillArea.left() + m_position, fillArea.bottom());
+
+    QRect markArea(ptop, pbot);
+    p->DrawRect(markArea,
+                true, colorSet[3],
+                true, 2, colorSet[3]);
+}
+
+void MythUICutPointBar::ClearAll()
+{
+    for (int i = 0; i < m_drawWidth; i++)
+        m_drawMap[i] = 0;
+    SetRedraw();
+}
Index: mythbuild/mythtv/libs/libmythui/mythuicutpointbar.h
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythui/mythuicutpointbar.h
@@ -0,0 +1,65 @@
+#ifndef MYTHUI_CUTPOINTBAR_H_
+#define MYTHUI_CUTPOINTBAR_H_
+
+#include "mythuitype.h"
+#include "mythuiimage.h"
+
+class MythFontProperties;
+
+/** \class MythUIProgressBar
+ *
+ *  \brief Progress bar widget.
+ *
+ */
+class MPUBLIC MythUICutPointBar : public MythUIType
+{
+  public:
+    MythUICutPointBar(MythUIType *parent, const QString &name);
+   ~MythUICutPointBar() { }
+
+    void Reset(void);
+
+    enum LayoutType { LayoutVertical, LayoutHorizontal };
+    enum EffectType { EffectReveal, EffectSlide, EffectAnimate };
+
+    void SetStart(int);
+    void SetUsed(int);
+    void SetTotal(int);
+
+    void setFillColor(QColor c)     { colorSet[0] = c; };
+    void setCutColor(QColor c)      { colorSet[1] = c; };
+    void setCutPointColor(QColor c) { colorSet[2] = c; };
+    void setPositionColor(QColor c) { colorSet[3] = c; };
+    void setInCutColor(QColor c)    { colorSet[4] = c; };
+
+    void InitWidth();
+    void ClearAll();
+    void SetRange(long long fstart, long long fend, long long fcount);
+    void SetCurrentPosition(long long fposition, long long fcount);
+
+  protected:
+    virtual void DrawSelf(MythPainter *p, int xoffset, int yoffset,
+                          int alphaMod, QRect clipRegion);
+
+    virtual bool ParseElement(
+        const QString &filename, QDomElement &element, bool showWarnings);
+    virtual void CopyFrom(MythUIType *base);
+    virtual void CreateCopy(MythUIType *parent);
+
+    LayoutType m_layout;
+    EffectType m_effect;
+
+    unsigned char *m_drawMap;
+    int            m_drawWidth;
+    int            m_position;
+
+    QColor         colorSet[5];
+
+    int m_total;
+    int m_start;
+    int m_current;
+
+    void CalculatePosition(void);
+};
+
+#endif
Index: mythbuild/mythtv/libs/libmythui/xmlparsebase.cpp
===================================================================
--- mythbuild.orig/mythtv/libs/libmythui/xmlparsebase.cpp
+++ mythbuild/mythtv/libs/libmythui/xmlparsebase.cpp
@@ -28,6 +28,8 @@
 #include "mythuispinbox.h"
 #include "mythuicheckbox.h"
 #include "mythuiprogressbar.h"
+#include "mythuicutpointbar.h"
+#include "mythuicutpointimage.h"
 #include "mythuigroup.h"
 #include "mythuiwebbrowser.h"
 #include "mythuiguidegrid.h"
@@ -246,6 +248,8 @@ void XMLParseBase::ParseChildren(const Q
                      type == "statetype" ||
                      type == "clock" ||
                      type == "progressbar" ||
+                     type == "cutpointbar" ||
+                     type == "cutpointimage" ||
                      type == "webbrowser" ||
                      type == "guidegrid" ||
                      type == "shape")
@@ -343,6 +347,10 @@ MythUIType *XMLParseBase::ParseUIType(
         uitype = new MythUIClock(parent, name);
     else if (type == "progressbar")
         uitype = new MythUIProgressBar(parent, name);
+    else if (type == "cutpointbar")
+        uitype = new MythUICutPointBar(parent, name);
+    else if (type == "cutpointimage")
+        uitype = new MythUICutPointImage(parent, name);
     else if (type == "webbrowser")
         uitype = new MythUIWebBrowser(parent, name);
     else if (type == "guidegrid")
@@ -432,6 +440,8 @@ MythUIType *XMLParseBase::ParseUIType(
                      info.tagName() == "statetype" ||
                      info.tagName() == "clock" ||
                      info.tagName() == "progressbar" ||
+                     info.tagName() == "cutpointbar" ||
+                     info.tagName() == "cutpointimage" ||
                      info.tagName() == "webbrowser" ||
                      info.tagName() == "guidegrid" ||
                      info.tagName() == "shape")
@@ -617,6 +627,8 @@ bool XMLParseBase::doLoad(const QString 
                          type == "window" ||
                          type == "clock" ||
                          type == "progressbar" ||
+                         type == "cutpointbar" ||
+                         type == "cutpointimage" ||
                          type == "webbrowser" ||
                          type == "guidegrid" ||
                          type == "shape")
Index: mythbuild/mythtv/libs/libmythui/mythuicutpointimage.cpp
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythui/mythuicutpointimage.cpp
@@ -0,0 +1,248 @@
+
+// Own header
+#include "mythuicutpointimage.h"
+
+// C++
+#include <algorithm>
+using namespace std;
+
+// qt
+#include <QDomDocument>
+#include <QPainter>
+#include <QSize>
+#include <QColor>
+
+// myth
+#include "mythverbose.h"
+#include "mythpainter.h"
+#include "mythimage.h"
+#include "mythmainwindow.h"
+
+#ifndef FUNCTIONLOGGER
+#define FUNCTIONLOGGER
+#endif
+
+MythUICutPointImage::MythUICutPointImage(MythUIType *parent, const QString &name)
+          : MythUIType(parent, name)
+{
+    m_image = NULL;
+
+    m_cutStatus = 0;
+    m_highlightFrame = false;
+    m_framenumber = -1;
+}
+
+MythUICutPointImage::~MythUICutPointImage()
+{
+    if (m_image)
+    {
+        m_image->DownRef();
+        m_image = NULL;
+    }
+}
+
+void MythUICutPointImage::Reset()
+{
+    if (m_image)
+    {
+        m_image->DownRef();
+        m_image = NULL;
+    }
+
+    MythUIType::Reset();
+}
+
+void MythUICutPointImage::DrawSelf(MythPainter *p, int xoffset, int yoffset,
+                          int alphaMod, QRect clipRect)
+{
+//FUNCTIONLOGGER;
+//    if (m_image)
+//        VERBOSE(VB_GENERAL, QString("Has Image 1 (null? %1), has pixmap %2").arg(m_image->isNull()).arg(m_pm != NULL));
+//    else
+//        VERBOSE(VB_GENERAL, QString("Has Image 0, has pixmap %1").arg(m_pm != NULL));
+
+    QRect area = GetArea();
+    area.translate(xoffset, yoffset);
+
+    if (!m_image || m_image->isNull())
+        UpdateImage();
+
+    if (m_image) {
+        p->DrawImage(area.x(), area.y(), m_image, alphaMod);
+    }
+}
+
+void MythUICutPointImage::SetPixmap(QPixmap * new_pmap, long long frame, int new_cutStatus)
+{
+//FUNCTIONLOGGER;
+    if (! new_pmap)
+    {
+        clearPixmap();
+        return;
+    }
+
+    if (frame == m_framenumber)
+    {
+        // No change in pixmap (?)
+        setCutStatus(new_cutStatus);
+        return;
+    }
+
+    m_pmap = *new_pmap;
+    m_framenumber = frame;
+
+    if (new_cutStatus >= 0 && new_cutStatus <= 3)
+        m_cutStatus = new_cutStatus;
+    else
+        m_cutStatus = 0;
+
+    UpdateImage();
+    SetRedraw();
+}
+
+void MythUICutPointImage::clearPixmap(bool dorefresh)
+{
+    if (! m_pmap.isNull())
+    {
+        m_pmap = QPixmap();
+
+        m_cutStatus = 0;
+        m_framenumber = -1;
+        UpdateImage();
+        if (dorefresh)
+            SetRedraw();
+    }
+}
+
+void MythUICutPointImage::setCutStatus(int new_cutStatus)
+{
+    if (new_cutStatus == m_cutStatus)
+        return;
+
+    if (new_cutStatus >= 0 && new_cutStatus <= 3)
+        m_cutStatus = new_cutStatus;
+    else
+        m_cutStatus = 0;
+
+    UpdateImage();
+    SetRedraw();
+}
+
+
+//void MythUICutPointImage::DrawRect(const QRect &area,const QBrush &fillBrush,
+//                           const QPen &linePen)
+void MythUICutPointImage::UpdateImage()
+{
+    if (m_image)
+    {
+        m_image->DownRef();
+        m_image = NULL;
+    }
+    QRect area = GetArea();
+
+    QImage image(QSize(area.width(), area.height()), QImage::Format_ARGB32);
+    QPainter painter(&image);
+
+    if (m_pmap.isNull())
+    {
+        QRect tmparea(0, 0, area.width(), area.height());
+        QColor drawcolor = QColor( 0, 0, 0);
+        painter.setBrush(QBrush(Qt::SolidPattern));
+        painter.setPen(QPen(drawcolor, 2));
+        painter.drawRect(tmparea);
+    }
+    else
+    {
+        QRect m_outer_border(0, 0, area.width(), area.height());
+        QRect m_inner_border(1, 1, area.width()-2, area.height()-2);
+        painter.drawPixmap(0, 0, m_pmap);
+
+        painter.setRenderHint(QPainter::Antialiasing);
+
+        painter.setBrush(QBrush(Qt::NoBrush));
+        painter.setPen(QPen(m_colorSet[0], 3));
+        painter.drawRect(m_outer_border);
+
+        if (m_cutStatus > 0)
+        {
+            painter.setPen(QPen(m_colorSet[m_cutStatus], 3));
+            painter.drawRect(m_inner_border);
+            painter.drawLine(m_inner_border.topLeft(), m_inner_border.bottomRight());
+            painter.drawLine(m_inner_border.bottomLeft(), m_inner_border.topRight());
+        }
+        if (m_highlightFrame)
+        {
+            // Overlay the image frame with the standard frame color
+            // This is used to highlight small frame #0
+            painter.setPen(QPen(m_highlightColor, 3));
+            painter.drawRect(m_outer_border);
+        }
+    }
+
+    painter.end();
+
+    m_image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
+    m_image->UpRef();
+    m_image->Assign(image);
+}
+
+bool MythUICutPointImage::ParseElement(
+    const QString &filename, QDomElement &element, bool showWarnings)
+{
+    if (element.tagName() == "outlinecolor")
+    {
+        m_colorSet[0] = QColor(getFirstText(element));
+    }
+    else if (element.tagName() == "cutcolor")
+    {
+        m_colorSet[1] = QColor(getFirstText(element));
+    }
+    else if (element.tagName() == "cutpointcolor")
+    {
+        m_colorSet[2] = m_colorSet[3] = QColor(getFirstText(element));
+    }
+    else if (element.tagName() == "highlightcolor")
+    {
+        m_highlightColor = QColor(getFirstText(element));
+        m_highlightFrame = true;
+    }
+    else
+    {
+        return MythUIType::ParseElement(filename, element, showWarnings);
+    }
+
+    return true;
+}
+
+void MythUICutPointImage::CopyFrom(MythUIType *base)
+{
+    MythUICutPointImage *shape = dynamic_cast<MythUICutPointImage *>(base);
+    if (!shape)
+    {
+        VERBOSE(VB_IMPORTANT, "ERROR, bad parsing");
+        return;
+    }
+
+    m_image = shape->m_image;
+    if (m_image)
+        m_image->UpRef();
+
+    m_pmap = shape->m_pmap ;
+    m_framenumber = shape->m_framenumber;
+    m_cutStatus = shape->m_cutStatus;
+    m_highlightFrame = shape->m_highlightFrame;
+    m_highlightColor = shape->m_highlightColor;
+
+    int i;
+    for (i = 0; i < 4; i++)
+        m_colorSet[i] = shape->m_colorSet[i];
+
+    MythUIType::CopyFrom(base);
+}
+
+void MythUICutPointImage::CreateCopy(MythUIType *parent)
+{
+    MythUICutPointImage *shape = new MythUICutPointImage(parent, objectName());
+    shape->CopyFrom(this);
+}
+
Index: mythbuild/mythtv/libs/libmythui/mythuicutpointimage.h
===================================================================
--- /dev/null
+++ mythbuild/mythtv/libs/libmythui/mythuicutpointimage.h
@@ -0,0 +1,52 @@
+#ifndef MYTHUICUTPOINTIMAGE_H_
+#define MYTHUICUTPOINTIMAGE_H_
+
+// QT headers
+#include <QColor>
+#include <QPen>
+#include <QBrush>
+#include <QLinearGradient>
+
+// Mythui headers
+#include "mythuitype.h"
+
+class MythImage;
+
+class MPUBLIC MythUICutPointImage : public MythUIType
+{
+  public:
+    MythUICutPointImage(MythUIType *parent, const QString &name);
+    ~MythUICutPointImage();
+
+    void Reset(void);
+
+    void SetPixmap(QPixmap * new_pmap, long long frame, int new_cutStatus);
+
+    void clearPixmap(bool dorefresh=true);
+    void setCutStatus(int new_cutStatus);
+
+  protected:
+    virtual void DrawSelf(MythPainter *p, int xoffset, int yoffset,
+                          int alphaMod, QRect clipRect);
+
+    virtual bool ParseElement(
+        const QString &filename, QDomElement &element, bool showWarnings);
+    virtual void CopyFrom(MythUIType *base);
+    virtual void CreateCopy(MythUIType *parent);
+
+    QLinearGradient parseGradient(const QDomElement &element);
+
+    void UpdateImage();
+
+  private:
+    MythImage     *m_image;
+
+    QPixmap        m_pmap;
+    long long      m_framenumber; // for consistency checking
+    int            m_cutStatus;
+    QColor         m_colorSet[4];
+    bool           m_highlightFrame;
+    QColor         m_highlightColor;
+};
+
+#endif
