Ticket #8901: cutlist_undo_stack_v4.patch

File cutlist_undo_stack_v4.patch, 19.6 KB (added by Jim Stichnoth <stichnot@…>, 9 years ago)

When doing "Exit without saving", make sure temporary marks are removed from the DB so they don't get loaded in the next edit session.

  • 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
    717class DeleteMap
    818{
    919  public:
    1020    DeleteMap(): m_editing(false),   m_nextCutStart(0), m_changed(true),
    11                  m_seekamountpos(4), m_seekamount(30) { }
     21                 m_seekamountpos(4), m_seekamount(30),
     22                 m_ctx(0), m_undoStackPointer(-1) { Push(""); }
    1223
     24    void SetPlayerContext(PlayerContext *ctx) { m_ctx = ctx; }
    1325    bool HandleAction(QString &action, uint64_t frame, uint64_t played,
    1426                      uint64_t total, double rate);
    1527    int  GetSeekAmount(void) { return m_seekamount; }
     
    2739
    2840    void SetMap(const frm_dir_map_t &map);
    2941    void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map);
    30     void SaveMap(uint64_t total, PlayerContext *ctx);
    31     void LoadMap(uint64_t total, PlayerContext *ctx);
     42    void SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave=false);
     43    void LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage="");
     44    bool LoadAutoSaveMap(uint64_t total, PlayerContext *ctx);
    3245    void CleanMap(uint64_t total);
    3346
    34     void Clear(void);
     47    void Clear(QString undoMessage="");
    3548    void ReverseAll(uint64_t total);
    36     void Add(uint64_t frame, uint64_t total, MarkTypes type);
     49    void Add(uint64_t frame, uint64_t total, MarkTypes type,
     50             QString undoMessage);
    3751    void NewCut(uint64_t frame, uint64_t total);
    38     void Delete(uint64_t frame, uint64_t total);
     52    void Delete(uint64_t frame, uint64_t total, QString undoMessage);
    3953    void Reverse(uint64_t frame, uint64_t total);
    4054    void MoveRelative(uint64_t frame, uint64_t total, bool right);
    4155    void Move(uint64_t frame, uint64_t to, uint64_t total);
     
    4963    void TrackerReset(uint64_t frame, uint64_t total);
    5064    bool TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to);
    5165
     66    bool Undo(void);
     67    bool Redo(void);
     68    bool HasUndo(void) { return m_undoStackPointer > 0; }
     69    bool HasRedo(void) { return m_undoStackPointer < m_undoStack.size() - 1; }
     70    QString GetUndoMessage(void);
     71    QString GetRedoMessage(void);
     72
    5273  private:
    5374    void Add(uint64_t frame, MarkTypes type);
    5475    MarkTypes Delete(uint64_t frame);
    5576
     77    void Push(QString undoMessage);
     78
    5679    bool          m_editing;
    5780    uint64_t      m_nextCutStart;
    5881    frm_dir_map_t m_deleteMap;
     
    6083    bool          m_changed;
    6184    int           m_seekamountpos;
    6285    int           m_seekamount;
     86    PlayerContext *m_ctx;
     87
     88    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == m_deleteMap
     89    QVector<DeleteMapUndoEntry> m_undoStack;
     90    int m_undoStackPointer;
    6391};
    6492
    6593#endif // DELETEMAP_H
  • 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 deleteMap.HasUndo(); }
     305    bool DeleteMapHasRedo(void) { return deleteMap.HasRedo(); }
     306    QString DeleteMapGetUndoMessage(void) { return deleteMap.GetUndoMessage(); }
     307    QString DeleteMapGetRedoMessage(void) { return deleteMap.GetRedoMessage(); }
    304308
    305309    // Decoder stuff..
    306310    VideoFrame *GetNextVideoFrame(bool allow_unsafe = true);
  • libs/libmythtv/tv_play.cpp

     
    773773            "Jump back 10x the normal amount"), ",,<");
    774774    REG_KEY("TV Editing", "BIGJUMPFWD",  QT_TRANSLATE_NOOP("MythControls",
    775775            "Jump forward 10x the normal amount"), ">,.");
     776    REG_KEY("TV Editing", "UNDO",        QT_TRANSLATE_NOOP("MythControls",
     777            "Undo"), "Ctrl+Z");
     778    REG_KEY("TV Editing", "REDO",        QT_TRANSLATE_NOOP("MythControls",
     779            "Redo"), "Ctrl+Y");
    776780
    777781    /* Teletext keys */
    778782    REG_KEY("Teletext Menu", "NEXTPAGE",    QT_TRANSLATE_NOOP("MythControls",
     
    90759079        if ("EDIT_CUT_POINTS" == type)
    90769080            osd->DialogAddButton(QObject::tr("Cut List Options"),
    90779081                                 "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
     9082        if (ctx->player->DeleteMapHasUndo())
     9083            osd->DialogAddButton(QObject::tr("Undo") + " - " +
     9084                                 ctx->player->DeleteMapGetUndoMessage(),
     9085                                 QString("DIALOG_CUTPOINT_UNDO_0"));
     9086        if (ctx->player->DeleteMapHasRedo())
     9087            osd->DialogAddButton(QObject::tr("Redo") + " - " +
     9088                                 ctx->player->DeleteMapGetRedoMessage(),
     9089                                 QString("DIALOG_CUTPOINT_REDO_0"));
    90789090    }
    90799091    else if ("CUT_LIST_OPTIONS" == type)
    90809092    {
  • 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 DeleteMap::Push(QString undoMessage)
     24{
     25    DeleteMapUndoEntry entry(m_deleteMap, undoMessage);
     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    SaveMap(0, m_ctx, true);
     32}
     33
     34bool DeleteMap::Undo(void)
     35{
     36    if (!HasUndo())
     37        return false;
     38    m_undoStackPointer --;
     39    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
     40    m_changed = true;
     41    SaveMap(0, m_ctx, true);
     42    return true;
     43}
     44
     45bool DeleteMap::Redo(void)
     46{
     47    if (!HasRedo())
     48        return false;
     49    m_undoStackPointer ++;
     50    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
     51    m_changed = true;
     52    SaveMap(0, m_ctx, true);
     53    return true;
     54}
     55
     56QString DeleteMap::GetUndoMessage(void)
     57{
     58    return (HasUndo() ? m_undoStack[m_undoStackPointer].message :
     59            QObject::tr("(No more undo operations)"));
     60}
     61
     62QString DeleteMap::GetRedoMessage(void)
     63{
     64    return (HasRedo() ? m_undoStack[m_undoStackPointer + 1].message :
     65            QObject::tr("(No more redo operations)"));
     66}
     67
    1368bool DeleteMap::HandleAction(QString &action, uint64_t frame,
    1469                             uint64_t played, uint64_t total, double rate)
    1570{
     
    2176    else if (action == "DOWN")
    2277        UpdateSeekAmount(-1, rate);
    2378    else if (action == "CLEARMAP")
    24         Clear();
     79        Clear(QObject::tr("Clear Cut List"));
    2580    else if (action == "INVERTMAP")
    2681        ReverseAll(total);
    2782    else if (action == "MOVEPREV")
     
    2984    else if (action == "MOVENEXT")
    3085        MoveRelative(frame, total, true);
    3186    else if (action == "CUTTOBEGINNING")
    32         Add(frame, total, MARK_CUT_END);
     87        Add(frame, total, MARK_CUT_END, QObject::tr("Cut To Beginning"));
    3388    else if (action == "CUTTOEND")
    34         Add(frame, total, MARK_CUT_START);
     89        Add(frame, total, MARK_CUT_START, QObject::tr("Cut To End"));
    3590    else if (action == "NEWCUT")
    3691        NewCut(frame, total);
    3792    else if (action == "DELETE")
    38         Delete(frame, total);
     93        Delete(frame, total, QObject::tr("Delete Cut Area"));
     94    else if (action == "UNDO")
     95        Undo();
     96    else if (action == "REDO")
     97        Redo();
    3998    else
    4099        handled = false;
    41100    return handled;
     
    150209}
    151210
    152211/// Clears the deleteMap.
    153 void DeleteMap::Clear(void)
     212void DeleteMap::Clear(QString undoMessage)
    154213{
    155214    m_deleteMap.clear();
    156215    m_changed = true;
     216    if (!undoMessage.isEmpty())
     217        Push(undoMessage);
    157218}
    158219
    159220/// Reverses the direction of each mark in the map.
     
    165226        Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
    166227                                                   MARK_CUT_END);
    167228    CleanMap(total);
     229    Push(QObject::tr("Invert Cut List"));
    168230}
    169231
    170232/**
     
    172234 *        existing redundant mark of that type is removed. This simplifies
    173235 *        the cleanup code.
    174236 */
    175 void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
     237void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
     238                    QString undoMessage)
    176239{
    177240    EDIT_CHECK
    178241    if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
     
    186249        {
    187250            // Delete the temporary mark before putting a real mark at its
    188251            // location
    189             Delete(frame, total);
     252            Delete(frame, total, "");
    190253        }
    191254        else // Don't add a mark on top of a mark
    192255            return;
     
    241304        Delete((uint64_t)remove);
    242305    Add(frame, type);
    243306    CleanMap(total);
     307    if (!undoMessage.isEmpty())
     308        Push(undoMessage);
    244309}
    245310
    246311/// Remove the mark at the given frame.
    247 void DeleteMap::Delete(uint64_t frame, uint64_t total)
     312void DeleteMap::Delete(uint64_t frame, uint64_t total, QString undoMessage)
    248313{
    249314    EDIT_CHECK
    250315    if (m_deleteMap.isEmpty())
     
    271336    if (prev != next)
    272337        Delete(next);
    273338    CleanMap(total);
     339    if (!undoMessage.isEmpty())
     340        Push(undoMessage);
    274341}
    275342
    276343/// Reverse the direction of the mark at the given frame.
     
    278345{
    279346    EDIT_CHECK
    280347    int type = Delete(frame);
    281     Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END);
     348    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END, "");
     349    Push(QObject::tr("Reverse Mark Direction"));
    282350}
    283351
    284352/// Add a new cut marker (to start or end a cut region)
     
    376444        Add(frame, MARK_PLACEHOLDER);
    377445
    378446    CleanMap(total);
     447    Push(QObject::tr("New Cut"));
    379448}
    380449
    381450/// Move the previous (!right) or next (right) cut to frame.
     
    397466        {
    398467            // If on a mark, don't collapse a cut region to 0;
    399468            // instead, delete the region
    400             Delete(frame, total);
     469            Delete(frame, total, QObject::tr("Delete Cut Area"));
    401470            return;
    402471        }
    403472        else if (MARK_PLACEHOLDER == type)
    404473        {
    405474            // Delete the temporary mark before putting a real mark at its
    406475            // location
    407             Delete(frame, total);
     476            Delete(frame, total, "");
    408477        }
    409478    }
    410479
     
    424493        else if (frame == total)
    425494            type = MARK_CUT_END;
    426495    }
    427     Add(to, total, type);
     496    Add(to, total, type, QObject::tr("Move Mark"));
    428497}
    429498
    430499/// Private addition to the deleteMap.
     
    582651    Clear();
    583652    m_deleteMap = map;
    584653    m_deleteMap.detach();
     654    Push(QObject::tr("Set New Cut List"));
    585655}
    586656
    587657/// Loads the given commercial break map into the deleteMap.
     
    593663        Add(it.key(), it.value() == MARK_COMM_START ?
    594664                MARK_CUT_START : MARK_CUT_END);
    595665    CleanMap(total);
     666    Push(QObject::tr("Load Commskip List"));
    596667}
    597668
    598669/// Loads the delete map from the database.
    599 void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
     670void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage)
    600671{
    601672    if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
    602673        return;
     
    606677    ctx->playingInfo->QueryCutList(m_deleteMap);
    607678    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    608679    CleanMap(total);
     680    if (!undoMessage.isEmpty())
     681        Push(undoMessage);
    609682}
    610683
     684/// Returns true if an auto-save map was loaded.
     685/// Does nothing and returns false if not.
     686bool DeleteMap::LoadAutoSaveMap(uint64_t total, PlayerContext *ctx)
     687{
     688    if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
     689        return false;
     690
     691    frm_dir_map_t tmpDeleteMap = m_deleteMap;
     692    Clear();
     693    ctx->LockPlayingInfo(__FILE__, __LINE__);
     694    bool result = ctx->playingInfo->QueryCutList(m_deleteMap, true);
     695    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
     696    CleanMap(total);
     697    if (result)
     698        Push(QObject::tr("Load Auto-Save Cut List"));
     699    else
     700        m_deleteMap = tmpDeleteMap;
     701
     702    return result;
     703}
     704
    611705/// Saves the delete map to the database.
    612 void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx)
     706void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave)
    613707{
    614708    if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
    615709        return;
    616710
     711    if (!isAutoSave)
     712    {
    617713    // Remove temporary placeholder marks
    618714    QMutableMapIterator<uint64_t, MarkTypes> it(m_deleteMap);
    619715    while (it.hasNext())
     
    627723    }
    628724
    629725    CleanMap(total);
     726    }
    630727    ctx->LockPlayingInfo(__FILE__, __LINE__);
    631728    ctx->playingInfo->SaveMarkupFlag(MARK_UPDATED_CUT);
    632     ctx->playingInfo->SaveCutList(m_deleteMap);
     729    ctx->playingInfo->SaveCutList(m_deleteMap, isAutoSave);
    633730    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
    634731}
    635732
  • libs/libmythtv/mythplayer.cpp

     
    33873387void MythPlayer::SetPlayerInfo(TV *tv, QWidget *widget,
    33883388                               bool frame_exact_seek, PlayerContext *ctx)
    33893389{
     3390    deleteMap.SetPlayerContext(ctx);
    33903391    m_tv = tv;
    33913392    parentWidget = widget;
    33923393    exactseeks   = frame_exact_seek;
     
    34123413    deleteMap.SetEditing(true);
    34133414    osd->DialogQuit();
    34143415    osd->HideAll();
     3416
     3417    bool loadedAutoSave = deleteMap.LoadAutoSaveMap(totalFrames, player_ctx);
     3418    (void)loadedAutoSave; // XXX - should we display an OSD message?
     3419
    34153420    deleteMap.UpdateSeekAmount(0, video_frame_rate);
    34163421    deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate,
    34173422                        player_ctx, osd);
     
    34263431void MythPlayer::DisableEdit(bool save)
    34273432{
    34283433    deleteMap.SetEditing(false, osd);
    3429     if (save)
    3430         deleteMap.SaveMap(totalFrames, player_ctx);
    3431     else
     3434    if (!save)
    34323435        deleteMap.LoadMap(totalFrames, player_ctx);
     3436    // Unconditionally save to remove temporary marks from the DB.
     3437    deleteMap.SaveMap(totalFrames, player_ctx);
    34333438    deleteMap.TrackerReset(framesPlayed, totalFrames);
    34343439    deleteMap.SetFileEditing(player_ctx, false);
    34353440    player_ctx->LockPlayingInfo(__FILE__, __LINE__);
     
    35253530        {
    35263531            if (IsInDelete(frame))
    35273532            {
    3528                 deleteMap.Delete(frame, totalFrames);
     3533                deleteMap.Delete(frame, totalFrames,
     3534                                 QObject::tr("Delete cut area"));
    35293535                refresh = true;
    35303536            }
    35313537        }
    35323538        else if (action == "REVERT")
    35333539        {
    3534             deleteMap.LoadMap(totalFrames, player_ctx);
     3540            deleteMap.LoadMap(totalFrames, player_ctx,
     3541                              QObject::tr("Load Cut List"));
    35353542            refresh = true;
    35363543        }
    35373544        else if (action == "REVERTEXIT")
  • libs/libmyth/programtypes.cpp

     
    2222    switch (type)
    2323    {
    2424        case MARK_UNSET:        return "UNSET";
     25        case MARK_TMP_CUT_END:  return "TMP_CUT_END";
     26        case MARK_TMP_CUT_START:return "TMP_CUT_START";
    2527        case MARK_UPDATED_CUT:  return "UPDATED_CUT";
    2628        case MARK_PLACEHOLDER:  return "PLACEHOLDER";
    2729        case MARK_CUT_END:      return "CUT_END";
  • libs/libmyth/programinfo.h

     
    533533                           bool forceCheckLocal = false) const;
    534534
    535535    // Edit flagging map
    536     void QueryCutList(frm_dir_map_t &) const;
    537     void SaveCutList(frm_dir_map_t &) const;
     536    bool QueryCutList(frm_dir_map_t &, bool loadAutosave=false) const;
     537    void SaveCutList(frm_dir_map_t &, bool isAutoSave=false) const;
    538538
    539539    // Commercial flagging map
    540540    void QueryCommBreakList(frm_dir_map_t &) const;
  • libs/libmyth/programinfo.cpp

     
    26852685    return kDisableAutoExpire;
    26862686}
    26872687
    2688 void ProgramInfo::QueryCutList(frm_dir_map_t &delMap) const
     2688bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
    26892689{
    2690     QueryMarkupMap(delMap, MARK_CUT_START);
    2691     QueryMarkupMap(delMap, MARK_CUT_END, true);
    2692     QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
     2690    frm_dir_map_t autosaveMap;
     2691    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
     2692    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
     2693    QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
     2694    bool result = !autosaveMap.isEmpty();
     2695
     2696    if (loadAutoSave)
     2697    {
     2698        // Convert the temporary marks into regular marks.
     2699        delMap.clear();
     2700        frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
     2701        for (; i != autosaveMap.constEnd(); ++i)
     2702        {
     2703            uint64_t frame = i.key();
     2704            MarkTypes mark = i.value();
     2705            if (mark == MARK_TMP_CUT_START)
     2706                mark = MARK_CUT_START;
     2707            else if (mark == MARK_TMP_CUT_END)
     2708                mark = MARK_CUT_END;
     2709            delMap[frame] = mark;
     2710        }
     2711    }
     2712    else
     2713    {
     2714        QueryMarkupMap(delMap, MARK_CUT_START);
     2715        QueryMarkupMap(delMap, MARK_CUT_END, true);
     2716        QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
     2717    }
     2718
     2719    return result;
    26932720}
    26942721
    2695 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap) const
     2722void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
    26962723{
    2697     ClearMarkupMap(MARK_CUT_START);
    2698     ClearMarkupMap(MARK_CUT_END);
     2724    if (!isAutoSave)
     2725    {
     2726        ClearMarkupMap(MARK_CUT_START);
     2727        ClearMarkupMap(MARK_CUT_END);
     2728    }
    26992729    ClearMarkupMap(MARK_PLACEHOLDER);
    2700     SaveMarkupMap(delMap);
     2730    ClearMarkupMap(MARK_TMP_CUT_START);
     2731    ClearMarkupMap(MARK_TMP_CUT_END);
    27012732
     2733    frm_dir_map_t tmpDelMap;
     2734    frm_dir_map_t::const_iterator i = delMap.constBegin();
     2735    for (; i != delMap.constEnd(); ++i)
     2736    {
     2737        uint64_t frame = i.key();
     2738        MarkTypes mark = i.value();
     2739        if (isAutoSave)
     2740        {
     2741            if (mark == MARK_CUT_START)
     2742                mark = MARK_TMP_CUT_START;
     2743            else if (mark == MARK_CUT_END)
     2744                mark = MARK_TMP_CUT_END;
     2745        }
     2746        tmpDelMap[frame] = mark;
     2747    }
     2748    SaveMarkupMap(tmpDelMap);
     2749
    27022750    if (IsRecording())
    27032751    {
    27042752        MSqlQuery query(MSqlQuery::InitCon());
  • libs/libmyth/programtypes.h

     
    4040typedef enum {
    4141    MARK_ALL           = -100,
    4242    MARK_UNSET         = -10,
     43    MARK_TMP_CUT_END   = -5,
     44    MARK_TMP_CUT_START = -4,
    4345    MARK_UPDATED_CUT   = -3,
    4446    MARK_PLACEHOLDER   = -2,
    4547    MARK_CUT_END       = 0,