Ticket #8901: cutlist_undo_stack_v1.patch

File cutlist_undo_stack_v1.patch, 19.1 KB (added by Jim Stichnoth <stichnot@…>, 9 years ago)
  • libs/libmythtv/deletemap.h

     
    44#include "programinfo.h"
    55#include "playercontext.h"
    66
     7class DeleteMap;
     8
     9typedef struct DeleteMapUndoEntry
     10{
     11    frm_dir_map_t deleteMap;
     12    QString message; // how we got from previous map to this map
     13    DeleteMapUndoEntry(frm_dir_map_t dm, QString msg);
     14    DeleteMapUndoEntry(void);
     15} DeleteMapUndoEntry;
     16
     17class DeleteMapUndoStack
     18{
     19 public:
     20    DeleteMapUndoStack(DeleteMap *map)
     21        : m_map(map), m_undoStackPointer(-1) {
     22        Push("");
     23    }
     24    void Push(QString message);
     25    bool Undo(void);
     26    bool Redo(void);
     27    bool HasUndo(void);
     28    bool HasRedo(void);
     29    QString GetUndoMessage(void);
     30    QString GetRedoMessage(void);
     31
     32 private:
     33    DeleteMap *m_map;
     34    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == *m_map
     35    // where m_deleteMap is from the current DeleteMap.
     36    QVector<DeleteMapUndoEntry> m_undoStack;
     37    int m_undoStackPointer;
     38};
     39
    740class DeleteMap
    841{
     42    friend class DeleteMapUndoStack;
    943  public:
    1044    DeleteMap(): m_editing(false),   m_nextCutStart(0), m_changed(true),
    1145                 m_seekamountpos(4), m_seekamount(30) { }
    1246
    1347    bool HandleAction(QString &action, uint64_t frame, uint64_t played,
    14                       uint64_t total, double rate);
     48                      uint64_t total, double rate,
     49                      DeleteMapUndoStack *undoStack);
    1550    int  GetSeekAmount(void) { return m_seekamount; }
    1651    void UpdateSeekAmount(int change, double framerate);
    1752    void SetSeekAmount(int amount) { m_seekamount = amount; }
     
    2560    bool IsFileEditing(PlayerContext *ctx);
    2661    bool IsEmpty(void);
    2762
    28     void SetMap(const frm_dir_map_t &map);
    29     void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map);
     63    void SetMap(const frm_dir_map_t &map, DeleteMapUndoStack *undoStack);
     64    void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map,
     65                          DeleteMapUndoStack *undoStack);
    3066    void SaveMap(uint64_t total, PlayerContext *ctx);
    31     void LoadMap(uint64_t total, PlayerContext *ctx);
     67    void LoadMap(uint64_t total, PlayerContext *ctx,
     68                 DeleteMapUndoStack *undoStack);
    3269    void CleanMap(uint64_t total);
    3370
    34     void Clear(void);
    35     void ReverseAll(uint64_t total);
    36     void Add(uint64_t frame, uint64_t total, MarkTypes type);
    37     void NewCut(uint64_t frame, uint64_t total);
    38     void Delete(uint64_t frame, uint64_t total);
    39     void Reverse(uint64_t frame, uint64_t total);
    40     void MoveRelative(uint64_t frame, uint64_t total, bool right);
    41     void Move(uint64_t frame, uint64_t to, uint64_t total);
     71    void Clear(DeleteMapUndoStack *undoStack);
     72    void ReverseAll(uint64_t total, DeleteMapUndoStack *undoStack);
     73    void Add(uint64_t frame, uint64_t total, MarkTypes type,
     74             DeleteMapUndoStack *undoStack, QString message);
     75    void NewCut(uint64_t frame, uint64_t total, DeleteMapUndoStack *undoStack);
     76    void Delete(uint64_t frame, uint64_t total,
     77                DeleteMapUndoStack *undoStack, QString message);
     78    void Reverse(uint64_t frame, uint64_t total, DeleteMapUndoStack *undoStack);
     79    void MoveRelative(uint64_t frame, uint64_t total, bool right,
     80                      DeleteMapUndoStack *undoStack);
     81    void Move(uint64_t frame, uint64_t to, uint64_t total, DeleteMapUndoStack *undoStack);
    4282
    4383    bool     IsInDelete(uint64_t frame);
    4484    uint64_t GetNearestMark(uint64_t frame, uint64_t total, bool right);
  • libs/libmythtv/mythplayer.h

     
    301301    uint64_t GetNearestMark(uint64_t frame, bool right);
    302302    bool IsTemporaryMark(uint64_t frame);
    303303    bool HasTemporaryMark(void);
     304    bool DeleteMapHasUndo(void) { return deleteMapUndoStack.HasUndo(); }
     305    bool DeleteMapHasRedo(void) { return deleteMapUndoStack.HasRedo(); }
     306    QString DeleteMapGetUndoMessage(void) { return deleteMapUndoStack.GetUndoMessage(); }
     307    QString DeleteMapGetRedoMessage(void) { return deleteMapUndoStack.GetRedoMessage(); }
    304308
    305309    // Decoder stuff..
    306310    VideoFrame *GetNextVideoFrame(bool allow_unsafe = true);
     
    652656    // Manual editing
    653657    DeleteMap  deleteMap;
    654658    bool       pausedBeforeEdit;
     659    DeleteMapUndoStack deleteMapUndoStack;
    655660
    656661    // Playback (output) speed control
    657662    /// Lock for next_play_speed and next_normal_speed
  • libs/libmythtv/tv_play.cpp

     
    774774            "Jump back 10x the normal amount"), ",,<");
    775775    REG_KEY("TV Editing", "BIGJUMPFWD",  QT_TRANSLATE_NOOP("MythControls",
    776776            "Jump forward 10x the normal amount"), ">,.");
     777    REG_KEY("TV Editing", "UNDO",        QT_TRANSLATE_NOOP("MythControls",
     778            "Undo"), "Ctrl+Z");
     779    REG_KEY("TV Editing", "REDO",        QT_TRANSLATE_NOOP("MythControls",
     780            "Redo"), "Ctrl+Y");
    777781
    778782    /* Teletext keys */
    779783    REG_KEY("Teletext Menu", "NEXTPAGE",    QT_TRANSLATE_NOOP("MythControls",
     
    93109314        if ("EDIT_CUT_POINTS" == type)
    93119315            osd->DialogAddButton(QObject::tr("Cut List Options"),
    93129316                                 "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
     9317        if (ctx->player->DeleteMapHasUndo())
     9318            osd->DialogAddButton(QObject::tr("Undo") + " - " +
     9319                                 ctx->player->DeleteMapGetUndoMessage(),
     9320                                 QString("DIALOG_CUTPOINT_UNDO_0"));
     9321        if (ctx->player->DeleteMapHasRedo())
     9322            osd->DialogAddButton(QObject::tr("Redo") + " - " +
     9323                                 ctx->player->DeleteMapGetRedoMessage(),
     9324                                 QString("DIALOG_CUTPOINT_REDO_0"));
    93139325    }
    93149326    else if ("CUT_LIST_OPTIONS" == type)
    93159327    {
  • libs/libmythtv/deletemap.cpp

     
    1010#define EDIT_CHECK if(!m_editing) \
    1111  { VERBOSE(VB_IMPORTANT, LOC_ERR + "Cannot edit outside editmode."); return; }
    1212
     13DeleteMapUndoEntry::DeleteMapUndoEntry(frm_dir_map_t dm, QString msg) :
     14    deleteMap(dm), message(msg) { }
     15
     16DeleteMapUndoEntry::DeleteMapUndoEntry(void)
     17{
     18    frm_dir_map_t dm;
     19    deleteMap = dm;
     20    message = "";
     21}
     22
     23void DeleteMapUndoStack::Push(QString message)
     24{
     25    DeleteMapUndoEntry entry(m_map->m_deleteMap, message);
     26    // Remove all "redo" entries
     27    while (m_undoStack.size() > m_undoStackPointer + 1)
     28        m_undoStack.pop_back();
     29    m_undoStack.append(entry);
     30    m_undoStackPointer ++;
     31}
     32
     33bool DeleteMapUndoStack::Undo(void)
     34{
     35    if (!HasUndo())
     36        return false;
     37    m_undoStackPointer --;
     38    m_map->m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
     39    m_map->m_changed = true;
     40    return true;
     41}
     42
     43bool DeleteMapUndoStack::Redo(void)
     44{
     45    if (!HasRedo())
     46        return false;
     47    m_undoStackPointer ++;
     48    m_map->m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
     49    m_map->m_changed = true;
     50    return true;
     51}
     52
     53bool DeleteMapUndoStack::HasUndo(void)
     54{
     55    return (m_undoStackPointer > 0);
     56}
     57
     58bool DeleteMapUndoStack::HasRedo(void)
     59{
     60    return (m_undoStackPointer < m_undoStack.size() - 1);
     61}
     62
     63QString DeleteMapUndoStack::GetUndoMessage(void)
     64{
     65    QString result;
     66    if (HasUndo())
     67        result = m_undoStack[m_undoStackPointer].message;
     68    else
     69        result = QObject::tr("(No more undo operations)");
     70    return result;
     71}
     72
     73QString DeleteMapUndoStack::GetRedoMessage(void)
     74{
     75    QString result;
     76    if (HasRedo())
     77        result = m_undoStack[m_undoStackPointer + 1].message;
     78    else
     79        result = QObject::tr("(No more redo operations)");
     80    return result;
     81}
     82
    1383bool DeleteMap::HandleAction(QString &action, uint64_t frame,
    14                              uint64_t played, uint64_t total, double rate)
     84                             uint64_t played, uint64_t total, double rate,
     85                             DeleteMapUndoStack *undoStack)
    1586{
    1687    bool handled = true;
    1788    if (action == "REVERSE")
    18         Reverse(frame, total);
     89        Reverse(frame, total, undoStack);
    1990    else if (action == "UP")
    2091        UpdateSeekAmount(1, rate);
    2192    else if (action == "DOWN")
    2293        UpdateSeekAmount(-1, rate);
    2394    else if (action == "CLEARMAP")
    24         Clear();
     95        Clear(undoStack);
    2596    else if (action == "INVERTMAP")
    26         ReverseAll(total);
     97        ReverseAll(total, undoStack);
    2798    else if (action == "MOVEPREV")
    28         MoveRelative(frame, total, false);
     99        MoveRelative(frame, total, false, undoStack);
    29100    else if (action == "MOVENEXT")
    30         MoveRelative(frame, total, true);
     101        MoveRelative(frame, total, true, undoStack);
    31102    else if (action == "CUTTOBEGINNING")
    32         Add(frame, total, MARK_CUT_END);
     103        Add(frame, total, MARK_CUT_END, undoStack,
     104            QObject::tr("Cut to beginning"));
    33105    else if (action == "CUTTOEND")
    34         Add(frame, total, MARK_CUT_START);
     106        Add(frame, total, MARK_CUT_START, undoStack,
     107            QObject::tr("Cut to beginning"));
    35108    else if (action == "NEWCUT")
    36         NewCut(frame, total);
     109        NewCut(frame, total, undoStack);
    37110    else if (action == "DELETE")
    38         Delete(frame, total);
     111        Delete(frame, total, undoStack,
     112               QObject::tr("Delete cut area"));
     113    else if (action == "UNDO")
     114        undoStack->Undo();
     115    else if (action == "REDO")
     116        undoStack->Redo();
    39117    else
    40118        handled = false;
    41119    return handled;
     
    150228}
    151229
    152230/// Clears the deleteMap.
    153 void DeleteMap::Clear(void)
     231void DeleteMap::Clear(DeleteMapUndoStack *undoStack)
    154232{
    155233    m_deleteMap.clear();
    156234    m_changed = true;
     235    if (undoStack)
     236        undoStack->Push(QObject::tr("Clear cut list"));
    157237}
    158238
    159239/// Reverses the direction of each mark in the map.
    160 void DeleteMap::ReverseAll(uint64_t total)
     240void DeleteMap::ReverseAll(uint64_t total, DeleteMapUndoStack *undoStack)
    161241{
    162242    EDIT_CHECK
    163243    frm_dir_map_t::Iterator it = m_deleteMap.begin();
     
    165245        Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
    166246                                                   MARK_CUT_END);
    167247    CleanMap(total);
     248    if (undoStack)
     249        undoStack->Push(QObject::tr("Invert cut list"));
    168250}
    169251
    170252/**
     
    172254 *        existing redundant mark of that type is removed. This simplifies
    173255 *        the cleanup code.
    174256 */
    175 void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
     257void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
     258                    DeleteMapUndoStack *undoStack, QString message)
    176259{
    177260    EDIT_CHECK
    178261    if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
     
    186269        {
    187270            // Delete the temporary mark before putting a real mark at its
    188271            // location
    189             Delete(frame, total);
     272            Delete(frame, total, NULL, "");
    190273        }
    191274        else // Don't add a mark on top of a mark
    192275            return;
     
    241324        Delete((uint64_t)remove);
    242325    Add(frame, type);
    243326    CleanMap(total);
     327    if (undoStack)
     328        undoStack->Push(message);
    244329}
    245330
    246331/// Remove the mark at the given frame.
    247 void DeleteMap::Delete(uint64_t frame, uint64_t total)
     332void DeleteMap::Delete(uint64_t frame, uint64_t total,
     333                       DeleteMapUndoStack *undoStack, QString message)
    248334{
    249335    EDIT_CHECK
    250336    if (m_deleteMap.isEmpty())
     
    271357    if (prev != next)
    272358        Delete(next);
    273359    CleanMap(total);
     360    if (undoStack)
     361        undoStack->Push(message);
    274362}
    275363
    276364/// Reverse the direction of the mark at the given frame.
    277 void DeleteMap::Reverse(uint64_t frame, uint64_t total)
     365void DeleteMap::Reverse(uint64_t frame, uint64_t total,
     366                        DeleteMapUndoStack *undoStack)
    278367{
    279368    EDIT_CHECK
    280369    int type = Delete(frame);
    281     Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END);
     370    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END,
     371        NULL, "");
     372    if (undoStack)
     373        undoStack->Push(QObject::tr("Reverse mark direction"));
    282374}
    283375
    284376/// Add a new cut marker (to start or end a cut region)
    285 void DeleteMap::NewCut(uint64_t frame, uint64_t total)
     377void DeleteMap::NewCut(uint64_t frame, uint64_t total,
     378                       DeleteMapUndoStack *undoStack)
    286379{
    287380    EDIT_CHECK
    288381
     
    376469        Add(frame, MARK_PLACEHOLDER);
    377470
    378471    CleanMap(total);
     472    if (undoStack)
     473        undoStack->Push(QObject::tr("New cut"));
    379474}
    380475
    381476/// Move the previous (!right) or next (right) cut to frame.
    382 void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right)
     477void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right,
     478                             DeleteMapUndoStack *undoStack)
    383479{
    384480    frm_dir_map_t::Iterator it = m_deleteMap.find(frame);
    385481    if (it != m_deleteMap.end())
     
    397493        {
    398494            // If on a mark, don't collapse a cut region to 0;
    399495            // instead, delete the region
    400             Delete(frame, total);
     496            Delete(frame, total, undoStack, QObject::tr("Delete cut area"));
    401497            return;
    402498        }
    403499        else if (MARK_PLACEHOLDER == type)
    404500        {
    405501            // Delete the temporary mark before putting a real mark at its
    406502            // location
    407             Delete(frame, total);
     503            Delete(frame, total, NULL, "");
    408504        }
    409505    }
    410506
    411507    uint64_t from = GetNearestMark(frame, total, right);
    412     Move(from, frame, total);
     508    Move(from, frame, total, undoStack);
    413509}
    414510
    415511/// Move an existing mark to a new frame.
    416 void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total)
     512void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total,
     513                     DeleteMapUndoStack *undoStack)
    417514{
    418515    EDIT_CHECK
    419516    MarkTypes type = Delete(frame);
     
    424521        else if (frame == total)
    425522            type = MARK_CUT_END;
    426523    }
    427     Add(to, total, type);
     524    Add(to, total, type, undoStack, QObject::tr("Move mark"));
    428525}
    429526
    430527/// Private addition to the deleteMap.
     
    577674}
    578675
    579676/// Use the given map.
    580 void DeleteMap::SetMap(const frm_dir_map_t &map)
     677void DeleteMap::SetMap(const frm_dir_map_t &map, DeleteMapUndoStack *undoStack)
    581678{
    582     Clear();
     679    Clear(NULL);
    583680    m_deleteMap = map;
    584681    m_deleteMap.detach();
     682    if (undoStack)
     683        undoStack->Push(QObject::tr("Set new cut list"));
    585684}
    586685
    587686/// Loads the given commercial break map into the deleteMap.
    588 void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map)
     687void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map,
     688                                 DeleteMapUndoStack *undoStack)
    589689{
    590     Clear();
     690    Clear(NULL);
    591691    frm_dir_map_t::Iterator it = map.begin();
    592692    for ( ; it != map.end(); ++it)
    593693        Add(it.key(), it.value() == MARK_COMM_START ?
    594694                MARK_CUT_START : MARK_CUT_END);
    595695    CleanMap(total);
     696    if (undoStack)
     697        undoStack->Push(QObject::tr("Load commskip list"));
    596698}
    597699
    598700/// Loads the delete map from the database.
    599 void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
     701void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx,
     702                        DeleteMapUndoStack *undoStack)
    600703{
    601704    if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
    602705        return;
    603706
    604     Clear();
     707    Clear(NULL);
    605708    ctx->LockPlayingInfo(__FILE__, __LINE__);
    606709    ctx->playingInfo->QueryCutList(m_deleteMap);
    607710    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    608711    CleanMap(total);
     712    if (undoStack)
     713        undoStack->Push(QObject::tr("Load cut list"));
    609714}
    610715
    611716/// Saves the delete map to the database.
  • libs/libmythtv/mythplayer.cpp

     
    227227      videoFilters(NULL),           FiltMan(new FilterManager()),
    228228
    229229      forcePositionMapSync(false),  pausedBeforeEdit(false),
     230      deleteMapUndoStack(&deleteMap),
    230231      // Playback (output) speed control
    231232      decoder_lock(QMutex::Recursive),
    232233      next_play_speed(1.0f),        next_normal_speed(true),
     
    10201021    if (ret > 0)
    10211022    {
    10221023        hasFullPositionMap = true;
    1023         deleteMap.LoadMap(totalFrames, player_ctx);
     1024        deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
    10241025        deleteMap.TrackerReset(0, totalFrames);
    10251026    }
    10261027
     
    33933394    if (save)
    33943395        deleteMap.SaveMap(totalFrames, player_ctx);
    33953396    else
    3396         deleteMap.LoadMap(totalFrames, player_ctx);
     3397        deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
    33973398    deleteMap.TrackerReset(framesPlayed, totalFrames);
    33983399    deleteMap.SetFileEditing(player_ctx, false);
    33993400    if (!pausedBeforeEdit)
     
    34363437            {
    34373438                frm_dir_map_t map;
    34383439                commBreakMap.GetMap(map);
    3439                 deleteMap.LoadCommBreakMap(totalFrames, map);
     3440                deleteMap.LoadCommBreakMap(totalFrames, map,
     3441                                           &deleteMapUndoStack);
    34403442            }
    34413443        }
    34423444        else if (action == "PREVCUT")
     
    34783480        }
    34793481        else if (action == "SELECT")
    34803482        {
    3481             deleteMap.NewCut(frame, totalFrames);
     3483            deleteMap.NewCut(frame, totalFrames, &deleteMapUndoStack);
    34823484            refresh = true;
    34833485        }
    34843486        else if (action == "DELETE")
    34853487        {
    34863488            if (IsInDelete(frame))
    34873489            {
    3488                 deleteMap.Delete(frame, totalFrames);
     3490                deleteMap.Delete(frame, totalFrames, &deleteMapUndoStack,
     3491                                 QObject::tr("Delete cut area"));
    34893492                refresh = true;
    34903493            }
    34913494        }
    34923495        else if (action == "REVERT")
    34933496        {
    3494             deleteMap.LoadMap(totalFrames, player_ctx);
     3497            deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
    34953498            refresh = true;
    34963499        }
    34973500        else if (action == "REVERTEXIT")
     
    35113514        }
    35123515        else
    35133516            handled = deleteMap.HandleAction(action, frame, framesPlayed,
    3514                                              totalFrames, video_frame_rate);
     3517                                             totalFrames, video_frame_rate,
     3518                                             &deleteMapUndoStack);
    35153519    }
    35163520
    35173521    if (handled && refresh)
     
    38343838        else
    38353839        {
    38363840            uint64_t oldnumber = number;
    3837             deleteMap.LoadMap(totalFrames, player_ctx);
     3841            deleteMap.LoadMap(totalFrames, player_ctx, &deleteMapUndoStack);
    38383842            commBreakMap.LoadMap(player_ctx, framesPlayed);
    38393843
    38403844            bool started_in_break_map = false;
     
    40304034
    40314035void MythPlayer::SetCutList(const frm_dir_map_t &newCutList)
    40324036{
    4033     deleteMap.SetMap(newCutList);
     4037    deleteMap.SetMap(newCutList, &deleteMapUndoStack);
    40344038}
    40354039
    40364040bool MythPlayer::WriteStoredData(RingBuffer *outRingBuffer,