Index: libs/libmythtv/deletemap.h
===================================================================
--- libs/libmythtv/deletemap.h	(revision 26115)
+++ libs/libmythtv/deletemap.h	(working copy)
@@ -4,14 +4,49 @@
 #include "programinfo.h"
 #include "playercontext.h"
 
+class DeleteMap;
+
+typedef struct DeleteMapUndoEntry
+{
+    frm_dir_map_t deleteMap;
+    QString message; // how we got from previous map to this map
+    DeleteMapUndoEntry(frm_dir_map_t dm, QString msg);
+    DeleteMapUndoEntry(void);
+} DeleteMapUndoEntry;
+
+class DeleteMapUndoStack
+{
+ public:
+    DeleteMapUndoStack(DeleteMap *map)
+        : m_map(map), m_undoStackPointer(-1) {
+        Push("");
+    }
+    void Push(QString message);
+    bool Undo(void);
+    bool Redo(void);
+    bool HasUndo(void);
+    bool HasRedo(void);
+    QString GetUndoMessage(void);
+    QString GetRedoMessage(void);
+
+ private:
+    DeleteMap *m_map;
+    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == *m_map
+    // where m_deleteMap is from the current DeleteMap.
+    QVector<DeleteMapUndoEntry> m_undoStack;
+    int m_undoStackPointer;
+};
+
 class DeleteMap
 {
+    friend class DeleteMapUndoStack;
   public:
     DeleteMap(): m_editing(false),   m_nextCutStart(0), m_changed(true),
                  m_seekamountpos(4), m_seekamount(30) { }
 
     bool HandleAction(QString &action, uint64_t frame, uint64_t played,
-                      uint64_t total, double rate);
+                      uint64_t total, double rate,
+                      DeleteMapUndoStack *undoStack);
     int  GetSeekAmount(void) { return m_seekamount; }
     void UpdateSeekAmount(int change, double framerate);
     void SetSeekAmount(int amount) { m_seekamount = amount; }
@@ -25,20 +60,25 @@
     bool IsFileEditing(PlayerContext *ctx);
     bool IsEmpty(void);
 
-    void SetMap(const frm_dir_map_t &map);
-    void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map);
+    void SetMap(const frm_dir_map_t &map, DeleteMapUndoStack *undoStack);
+    void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map,
+                          DeleteMapUndoStack *undoStack);
     void SaveMap(uint64_t total, PlayerContext *ctx);
-    void LoadMap(uint64_t total, PlayerContext *ctx);
+    void LoadMap(uint64_t total, PlayerContext *ctx,
+                 DeleteMapUndoStack *undoStack);
     void CleanMap(uint64_t total);
 
-    void Clear(void);
-    void ReverseAll(uint64_t total);
-    void Add(uint64_t frame, uint64_t total, MarkTypes type);
-    void NewCut(uint64_t frame, uint64_t total);
-    void Delete(uint64_t frame, uint64_t total);
-    void Reverse(uint64_t frame, uint64_t total);
-    void MoveRelative(uint64_t frame, uint64_t total, bool right);
-    void Move(uint64_t frame, uint64_t to, uint64_t total);
+    void Clear(DeleteMapUndoStack *undoStack);
+    void ReverseAll(uint64_t total, DeleteMapUndoStack *undoStack);
+    void Add(uint64_t frame, uint64_t total, MarkTypes type,
+             DeleteMapUndoStack *undoStack, QString message);
+    void NewCut(uint64_t frame, uint64_t total, DeleteMapUndoStack *undoStack);
+    void Delete(uint64_t frame, uint64_t total,
+                DeleteMapUndoStack *undoStack, QString message);
+    void Reverse(uint64_t frame, uint64_t total, DeleteMapUndoStack *undoStack);
+    void MoveRelative(uint64_t frame, uint64_t total, bool right,
+                      DeleteMapUndoStack *undoStack);
+    void Move(uint64_t frame, uint64_t to, uint64_t total, DeleteMapUndoStack *undoStack);
 
     bool     IsInDelete(uint64_t frame);
     uint64_t GetNearestMark(uint64_t frame, uint64_t total, bool right);
Index: libs/libmythtv/mythplayer.h
===================================================================
--- libs/libmythtv/mythplayer.h	(revision 26115)
+++ libs/libmythtv/mythplayer.h	(working copy)
@@ -301,6 +301,10 @@
     uint64_t GetNearestMark(uint64_t frame, bool right);
     bool IsTemporaryMark(uint64_t frame);
     bool HasTemporaryMark(void);
+    bool DeleteMapHasUndo(void) { return deleteMapUndoStack.HasUndo(); }
+    bool DeleteMapHasRedo(void) { return deleteMapUndoStack.HasRedo(); }
+    QString DeleteMapGetUndoMessage(void) { return deleteMapUndoStack.GetUndoMessage(); }
+    QString DeleteMapGetRedoMessage(void) { return deleteMapUndoStack.GetRedoMessage(); }
 
     // Decoder stuff..
     VideoFrame *GetNextVideoFrame(bool allow_unsafe = true);
@@ -652,6 +656,7 @@
     // Manual editing
     DeleteMap  deleteMap;
     bool       pausedBeforeEdit;
+    DeleteMapUndoStack deleteMapUndoStack;
 
     // Playback (output) speed control
     /// Lock for next_play_speed and next_normal_speed
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp	(revision 26115)
+++ libs/libmythtv/tv_play.cpp	(working copy)
@@ -774,6 +774,10 @@
             "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", "UNDO",        QT_TRANSLATE_NOOP("MythControls",
+            "Undo"), "Ctrl+Z");
+    REG_KEY("TV Editing", "REDO",        QT_TRANSLATE_NOOP("MythControls",
+            "Redo"), "Ctrl+Y");
 
     /* Teletext keys */
     REG_KEY("Teletext Menu", "NEXTPAGE",    QT_TRANSLATE_NOOP("MythControls",
@@ -9310,6 +9314,14 @@
         if ("EDIT_CUT_POINTS" == type)
             osd->DialogAddButton(QObject::tr("Cut List Options"),
                                  "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
+        if (ctx->player->DeleteMapHasUndo())
+            osd->DialogAddButton(QObject::tr("Undo") + " - " +
+                                 ctx->player->DeleteMapGetUndoMessage(),
+                                 QString("DIALOG_CUTPOINT_UNDO_0"));
+        if (ctx->player->DeleteMapHasRedo())
+            osd->DialogAddButton(QObject::tr("Redo") + " - " +
+                                 ctx->player->DeleteMapGetRedoMessage(),
+                                 QString("DIALOG_CUTPOINT_REDO_0"));
     }
     else if ("CUT_LIST_OPTIONS" == type)
     {
Index: libs/libmythtv/deletemap.cpp
===================================================================
--- libs/libmythtv/deletemap.cpp	(revision 26115)
+++ libs/libmythtv/deletemap.cpp	(working copy)
@@ -10,32 +10,110 @@
 #define EDIT_CHECK if(!m_editing) \
   { VERBOSE(VB_IMPORTANT, LOC_ERR + "Cannot edit outside editmode."); return; }
 
+DeleteMapUndoEntry::DeleteMapUndoEntry(frm_dir_map_t dm, QString msg) :
+    deleteMap(dm), message(msg) { }
+
+DeleteMapUndoEntry::DeleteMapUndoEntry(void)
+{
+    frm_dir_map_t dm;
+    deleteMap = dm;
+    message = "";
+}
+
+void DeleteMapUndoStack::Push(QString message)
+{
+    DeleteMapUndoEntry entry(m_map->m_deleteMap, message);
+    // Remove all "redo" entries
+    while (m_undoStack.size() > m_undoStackPointer + 1)
+        m_undoStack.pop_back();
+    m_undoStack.append(entry);
+    m_undoStackPointer ++;
+}
+
+bool DeleteMapUndoStack::Undo(void)
+{
+    if (!HasUndo())
+        return false;
+    m_undoStackPointer --;
+    m_map->m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_map->m_changed = true;
+    return true;
+}
+
+bool DeleteMapUndoStack::Redo(void)
+{
+    if (!HasRedo())
+        return false;
+    m_undoStackPointer ++;
+    m_map->m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_map->m_changed = true;
+    return true;
+}
+
+bool DeleteMapUndoStack::HasUndo(void)
+{
+    return (m_undoStackPointer > 0);
+}
+
+bool DeleteMapUndoStack::HasRedo(void)
+{
+    return (m_undoStackPointer < m_undoStack.size() - 1);
+}
+
+QString DeleteMapUndoStack::GetUndoMessage(void)
+{
+    QString result;
+    if (HasUndo())
+        result = m_undoStack[m_undoStackPointer].message;
+    else
+        result = QObject::tr("(No more undo operations)");
+    return result;
+}
+
+QString DeleteMapUndoStack::GetRedoMessage(void)
+{
+    QString result;
+    if (HasRedo())
+        result = m_undoStack[m_undoStackPointer + 1].message;
+    else
+        result = QObject::tr("(No more redo operations)");
+    return result;
+}
+
 bool DeleteMap::HandleAction(QString &action, uint64_t frame,
-                             uint64_t played, uint64_t total, double rate)
+                             uint64_t played, uint64_t total, double rate,
+                             DeleteMapUndoStack *undoStack)
 {
     bool handled = true;
     if (action == "REVERSE")
-        Reverse(frame, total);
+        Reverse(frame, total, undoStack);
     else if (action == "UP")
         UpdateSeekAmount(1, rate);
     else if (action == "DOWN")
         UpdateSeekAmount(-1, rate);
     else if (action == "CLEARMAP")
-        Clear();
+        Clear(undoStack);
     else if (action == "INVERTMAP")
-        ReverseAll(total);
+        ReverseAll(total, undoStack);
     else if (action == "MOVEPREV")
-        MoveRelative(frame, total, false);
+        MoveRelative(frame, total, false, undoStack);
     else if (action == "MOVENEXT")
-        MoveRelative(frame, total, true);
+        MoveRelative(frame, total, true, undoStack);
     else if (action == "CUTTOBEGINNING")
-        Add(frame, total, MARK_CUT_END);
+        Add(frame, total, MARK_CUT_END, undoStack,
+            QObject::tr("Cut to beginning"));
     else if (action == "CUTTOEND")
-        Add(frame, total, MARK_CUT_START);
+        Add(frame, total, MARK_CUT_START, undoStack,
+            QObject::tr("Cut to beginning"));
     else if (action == "NEWCUT")
-        NewCut(frame, total);
+        NewCut(frame, total, undoStack);
     else if (action == "DELETE")
-        Delete(frame, total);
+        Delete(frame, total, undoStack,
+               QObject::tr("Delete cut area"));
+    else if (action == "UNDO")
+        undoStack->Undo();
+    else if (action == "REDO")
+        undoStack->Redo();
     else
         handled = false;
     return handled;
@@ -150,14 +228,16 @@
 }
 
 /// Clears the deleteMap.
-void DeleteMap::Clear(void)
+void DeleteMap::Clear(DeleteMapUndoStack *undoStack)
 {
     m_deleteMap.clear();
     m_changed = true;
+    if (undoStack)
+        undoStack->Push(QObject::tr("Clear cut list"));
 }
 
 /// Reverses the direction of each mark in the map.
-void DeleteMap::ReverseAll(uint64_t total)
+void DeleteMap::ReverseAll(uint64_t total, DeleteMapUndoStack *undoStack)
 {
     EDIT_CHECK
     frm_dir_map_t::Iterator it = m_deleteMap.begin();
@@ -165,6 +245,8 @@
         Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
                                                    MARK_CUT_END);
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(QObject::tr("Invert cut list"));
 }
 
 /**
@@ -172,7 +254,8 @@
  *        existing redundant mark of that type is removed. This simplifies
  *        the cleanup code.
  */
-void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
+void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
+                    DeleteMapUndoStack *undoStack, QString message)
 {
     EDIT_CHECK
     if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
@@ -186,7 +269,7 @@
         {
             // Delete the temporary mark before putting a real mark at its
             // location
-            Delete(frame, total);
+            Delete(frame, total, NULL, "");
         }
         else // Don't add a mark on top of a mark
             return;
@@ -241,10 +324,13 @@
         Delete((uint64_t)remove);
     Add(frame, type);
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(message);
 }
 
 /// Remove the mark at the given frame.
-void DeleteMap::Delete(uint64_t frame, uint64_t total)
+void DeleteMap::Delete(uint64_t frame, uint64_t total,
+                       DeleteMapUndoStack *undoStack, QString message)
 {
     EDIT_CHECK
     if (m_deleteMap.isEmpty())
@@ -271,18 +357,25 @@
     if (prev != next)
         Delete(next);
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(message);
 }
 
 /// Reverse the direction of the mark at the given frame.
-void DeleteMap::Reverse(uint64_t frame, uint64_t total)
+void DeleteMap::Reverse(uint64_t frame, uint64_t total,
+                        DeleteMapUndoStack *undoStack)
 {
     EDIT_CHECK
     int type = Delete(frame);
-    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END);
+    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END,
+        NULL, "");
+    if (undoStack)
+        undoStack->Push(QObject::tr("Reverse mark direction"));
 }
 
 /// Add a new cut marker (to start or end a cut region)
-void DeleteMap::NewCut(uint64_t frame, uint64_t total)
+void DeleteMap::NewCut(uint64_t frame, uint64_t total,
+                       DeleteMapUndoStack *undoStack)
 {
     EDIT_CHECK
 
@@ -376,10 +469,13 @@
         Add(frame, MARK_PLACEHOLDER);
 
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(QObject::tr("New cut"));
 }
 
 /// Move the previous (!right) or next (right) cut to frame.
-void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right)
+void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right,
+                             DeleteMapUndoStack *undoStack)
 {
     frm_dir_map_t::Iterator it = m_deleteMap.find(frame);
     if (it != m_deleteMap.end())
@@ -397,23 +493,24 @@
         {
             // If on a mark, don't collapse a cut region to 0;
             // instead, delete the region
-            Delete(frame, total);
+            Delete(frame, total, undoStack, QObject::tr("Delete cut area"));
             return;
         }
         else if (MARK_PLACEHOLDER == type)
         {
             // Delete the temporary mark before putting a real mark at its
             // location
-            Delete(frame, total);
+            Delete(frame, total, NULL, "");
         }
     }
 
     uint64_t from = GetNearestMark(frame, total, right);
-    Move(from, frame, total);
+    Move(from, frame, total, undoStack);
 }
 
 /// Move an existing mark to a new frame.
-void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total)
+void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total,
+                     DeleteMapUndoStack *undoStack)
 {
     EDIT_CHECK
     MarkTypes type = Delete(frame);
@@ -424,7 +521,7 @@
         else if (frame == total)
             type = MARK_CUT_END;
     }
-    Add(to, total, type);
+    Add(to, total, type, undoStack, QObject::tr("Move mark"));
 }
 
 /// Private addition to the deleteMap.
@@ -577,35 +674,43 @@
 }
 
 /// Use the given map.
-void DeleteMap::SetMap(const frm_dir_map_t &map)
+void DeleteMap::SetMap(const frm_dir_map_t &map, DeleteMapUndoStack *undoStack)
 {
-    Clear();
+    Clear(NULL);
     m_deleteMap = map;
     m_deleteMap.detach();
+    if (undoStack)
+        undoStack->Push(QObject::tr("Set new cut list"));
 }
 
 /// Loads the given commercial break map into the deleteMap.
-void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map)
+void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map,
+                                 DeleteMapUndoStack *undoStack)
 {
-    Clear();
+    Clear(NULL);
     frm_dir_map_t::Iterator it = map.begin();
     for ( ; it != map.end(); ++it)
         Add(it.key(), it.value() == MARK_COMM_START ?
                 MARK_CUT_START : MARK_CUT_END);
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(QObject::tr("Load commskip list"));
 }
 
 /// Loads the delete map from the database.
-void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
+void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx,
+                        DeleteMapUndoStack *undoStack)
 {
     if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
         return;
 
-    Clear();
+    Clear(NULL);
     ctx->LockPlayingInfo(__FILE__, __LINE__);
     ctx->playingInfo->QueryCutList(m_deleteMap);
     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
     CleanMap(total);
+    if (undoStack)
+        undoStack->Push(QObject::tr("Load cut list"));
 }
 
 /// Saves the delete map to the database.
Index: libs/libmythtv/mythplayer.cpp
===================================================================
--- libs/libmythtv/mythplayer.cpp	(revision 26115)
+++ libs/libmythtv/mythplayer.cpp	(working copy)
@@ -227,6 +227,7 @@
       videoFilters(NULL),           FiltMan(new FilterManager()),
 
       forcePositionMapSync(false),  pausedBeforeEdit(false),
+      deleteMapUndoStack(&deleteMap),
       // Playback (output) speed control
       decoder_lock(QMutex::Recursive),
       next_play_speed(1.0f),        next_normal_speed(true),
@@ -1020,7 +1021,7 @@
     if (ret > 0)
     {
         hasFullPositionMap = true;
-        deleteMap.LoadMap(totalFrames, player_ctx);
+        deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
         deleteMap.TrackerReset(0, totalFrames);
     }
 
@@ -3393,7 +3394,7 @@
     if (save)
         deleteMap.SaveMap(totalFrames, player_ctx);
     else
-        deleteMap.LoadMap(totalFrames, player_ctx);
+        deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
     deleteMap.TrackerReset(framesPlayed, totalFrames);
     deleteMap.SetFileEditing(player_ctx, false);
     if (!pausedBeforeEdit)
@@ -3436,7 +3437,8 @@
             {
                 frm_dir_map_t map;
                 commBreakMap.GetMap(map);
-                deleteMap.LoadCommBreakMap(totalFrames, map);
+                deleteMap.LoadCommBreakMap(totalFrames, map,
+                                           &deleteMapUndoStack);
             }
         }
         else if (action == "PREVCUT")
@@ -3478,20 +3480,21 @@
         }
         else if (action == "SELECT")
         {
-            deleteMap.NewCut(frame, totalFrames);
+            deleteMap.NewCut(frame, totalFrames, &deleteMapUndoStack);
             refresh = true;
         }
         else if (action == "DELETE")
         {
             if (IsInDelete(frame))
             {
-                deleteMap.Delete(frame, totalFrames);
+                deleteMap.Delete(frame, totalFrames, &deleteMapUndoStack,
+                                 QObject::tr("Delete cut area"));
                 refresh = true;
             }
         }
         else if (action == "REVERT")
         {
-            deleteMap.LoadMap(totalFrames, player_ctx);
+            deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
             refresh = true;
         }
         else if (action == "REVERTEXIT")
@@ -3511,7 +3514,8 @@
         }
         else
             handled = deleteMap.HandleAction(action, frame, framesPlayed,
-                                             totalFrames, video_frame_rate);
+                                             totalFrames, video_frame_rate,
+                                             &deleteMapUndoStack);
     }
 
     if (handled && refresh)
@@ -3834,7 +3838,7 @@
         else
         {
             uint64_t oldnumber = number;
-            deleteMap.LoadMap(totalFrames, player_ctx);
+            deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
             commBreakMap.LoadMap(player_ctx, framesPlayed);
 
             bool started_in_break_map = false;
@@ -4030,7 +4034,7 @@
 
 void MythPlayer::SetCutList(const frm_dir_map_t &newCutList)
 {
-    deleteMap.SetMap(newCutList);
+    deleteMap.SetMap(newCutList, &deleteMapUndoStack);
 }
 
 bool MythPlayer::WriteStoredData(RingBuffer *outRingBuffer,
