Index: libs/libmythtv/deletemap.h
===================================================================
--- libs/libmythtv/deletemap.h	(revision 26193)
+++ libs/libmythtv/deletemap.h	(working copy)
@@ -4,11 +4,22 @@
 #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 DeleteMap
 {
   public:
     DeleteMap(): m_editing(false),   m_nextCutStart(0), m_changed(true),
-                 m_seekamountpos(4), m_seekamount(30) { }
+                 m_seekamountpos(4), m_seekamount(30),
+                 m_undoStackPointer(-1) { Push(""); }
 
     bool HandleAction(QString &action, uint64_t frame, uint64_t played,
                       uint64_t total, double rate);
@@ -33,9 +44,10 @@
 
     void Clear(void);
     void ReverseAll(uint64_t total);
-    void Add(uint64_t frame, uint64_t total, MarkTypes type);
+    void Add(uint64_t frame, uint64_t total, MarkTypes type,
+             QString undoMessage);
     void NewCut(uint64_t frame, uint64_t total);
-    void Delete(uint64_t frame, uint64_t total);
+    void Delete(uint64_t frame, uint64_t total, QString undoMessage);
     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);
@@ -49,10 +61,19 @@
     void TrackerReset(uint64_t frame, uint64_t total);
     bool TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to);
 
+    bool Undo(void);
+    bool Redo(void);
+    bool HasUndo(void) { return m_undoStackPointer > 0; }
+    bool HasRedo(void) { return m_undoStackPointer < m_undoStack.size() - 1; }
+    QString GetUndoMessage(void);
+    QString GetRedoMessage(void);
+
   private:
     void Add(uint64_t frame, MarkTypes type);
     MarkTypes Delete(uint64_t frame);
 
+    void Push(QString undoMessage);
+
     bool          m_editing;
     uint64_t      m_nextCutStart;
     frm_dir_map_t m_deleteMap;
@@ -60,6 +81,10 @@
     bool          m_changed;
     int           m_seekamountpos;
     int           m_seekamount;
+
+    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == m_deleteMap
+    QVector<DeleteMapUndoEntry> m_undoStack;
+    int m_undoStackPointer;
 };
 
 #endif // DELETEMAP_H
Index: libs/libmythtv/mythplayer.h
===================================================================
--- libs/libmythtv/mythplayer.h	(revision 26193)
+++ 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 deleteMap.HasUndo(); }
+    bool DeleteMapHasRedo(void) { return deleteMap.HasRedo(); }
+    QString DeleteMapGetUndoMessage(void) { return deleteMap.GetUndoMessage(); }
+    QString DeleteMapGetRedoMessage(void) { return deleteMap.GetRedoMessage(); }
 
     // Decoder stuff..
     VideoFrame *GetNextVideoFrame(bool allow_unsafe = true);
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp	(revision 26193)
+++ 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 26193)
+++ libs/libmythtv/deletemap.cpp	(working copy)
@@ -10,6 +10,58 @@
 #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 DeleteMap::Push(QString undoMessage)
+{
+    DeleteMapUndoEntry entry(m_deleteMap, undoMessage);
+    // Remove all "redo" entries
+    while (m_undoStack.size() > m_undoStackPointer + 1)
+        m_undoStack.pop_back();
+    m_undoStack.append(entry);
+    m_undoStackPointer ++;
+}
+
+bool DeleteMap::Undo(void)
+{
+    if (!HasUndo())
+        return false;
+    m_undoStackPointer --;
+    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_changed = true;
+    return true;
+}
+
+bool DeleteMap::Redo(void)
+{
+    if (!HasRedo())
+        return false;
+    m_undoStackPointer ++;
+    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_changed = true;
+    return true;
+}
+
+QString DeleteMap::GetUndoMessage(void)
+{
+    return (HasUndo() ? m_undoStack[m_undoStackPointer].message :
+            QObject::tr("(No more undo operations)"));
+}
+
+QString DeleteMap::GetRedoMessage(void)
+{
+    return (HasRedo() ? m_undoStack[m_undoStackPointer + 1].message :
+            QObject::tr("(No more redo operations)"));
+}
+
 bool DeleteMap::HandleAction(QString &action, uint64_t frame,
                              uint64_t played, uint64_t total, double rate)
 {
@@ -29,13 +81,17 @@
     else if (action == "MOVENEXT")
         MoveRelative(frame, total, true);
     else if (action == "CUTTOBEGINNING")
-        Add(frame, total, MARK_CUT_END);
+        Add(frame, total, MARK_CUT_END, QObject::tr("Cut To Beginning"));
     else if (action == "CUTTOEND")
-        Add(frame, total, MARK_CUT_START);
+        Add(frame, total, MARK_CUT_START, QObject::tr("Cut To End"));
     else if (action == "NEWCUT")
         NewCut(frame, total);
     else if (action == "DELETE")
-        Delete(frame, total);
+        Delete(frame, total, QObject::tr("Delete Cut Area"));
+    else if (action == "UNDO")
+        Undo();
+    else if (action == "REDO")
+        Redo();
     else
         handled = false;
     return handled;
@@ -154,6 +210,7 @@
 {
     m_deleteMap.clear();
     m_changed = true;
+    Push(QObject::tr("Clear Cut List"));
 }
 
 /// Reverses the direction of each mark in the map.
@@ -165,6 +222,7 @@
         Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
                                                    MARK_CUT_END);
     CleanMap(total);
+    Push(QObject::tr("Invert Cut List"));
 }
 
 /**
@@ -172,7 +230,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,
+                    QString undoMessage)
 {
     EDIT_CHECK
     if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
@@ -186,7 +245,7 @@
         {
             // Delete the temporary mark before putting a real mark at its
             // location
-            Delete(frame, total);
+            Delete(frame, total, "");
         }
         else // Don't add a mark on top of a mark
             return;
@@ -241,10 +300,12 @@
         Delete((uint64_t)remove);
     Add(frame, type);
     CleanMap(total);
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
 /// 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, QString undoMessage)
 {
     EDIT_CHECK
     if (m_deleteMap.isEmpty())
@@ -271,6 +332,8 @@
     if (prev != next)
         Delete(next);
     CleanMap(total);
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
 /// Reverse the direction of the mark at the given frame.
@@ -278,7 +341,8 @@
 {
     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, "");
+    Push(QObject::tr("Reverse Mark Direction"));
 }
 
 /// Add a new cut marker (to start or end a cut region)
@@ -376,6 +440,7 @@
         Add(frame, MARK_PLACEHOLDER);
 
     CleanMap(total);
+    Push(QObject::tr("New Cut"));
 }
 
 /// Move the previous (!right) or next (right) cut to frame.
@@ -397,14 +462,14 @@
         {
             // If on a mark, don't collapse a cut region to 0;
             // instead, delete the region
-            Delete(frame, total);
+            Delete(frame, total, 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, "");
         }
     }
 
@@ -424,7 +489,7 @@
         else if (frame == total)
             type = MARK_CUT_END;
     }
-    Add(to, total, type);
+    Add(to, total, type, QObject::tr("Move Mark"));
 }
 
 /// Private addition to the deleteMap.
@@ -582,6 +647,7 @@
     Clear();
     m_deleteMap = map;
     m_deleteMap.detach();
+    Push(QObject::tr("Set New Cut List"));
 }
 
 /// Loads the given commercial break map into the deleteMap.
@@ -593,6 +659,7 @@
         Add(it.key(), it.value() == MARK_COMM_START ?
                 MARK_CUT_START : MARK_CUT_END);
     CleanMap(total);
+    Push(QObject::tr("Load Commskip List"));
 }
 
 /// Loads the delete map from the database.
@@ -606,6 +673,7 @@
     ctx->playingInfo->QueryCutList(m_deleteMap);
     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
     CleanMap(total);
+    Push(QObject::tr("Load Cut List"));
 }
 
 /// Saves the delete map to the database.
Index: libs/libmythtv/mythplayer.cpp
===================================================================
--- libs/libmythtv/mythplayer.cpp	(revision 26193)
+++ libs/libmythtv/mythplayer.cpp	(working copy)
@@ -3485,7 +3485,8 @@
         {
             if (IsInDelete(frame))
             {
-                deleteMap.Delete(frame, totalFrames);
+                deleteMap.Delete(frame, totalFrames,
+                                 QObject::tr("Delete cut area"));
                 refresh = true;
             }
         }
