Ticket #8901: cutlist_undo_stack_v3.patch

File cutlist_undo_stack_v3.patch, 19.0 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
    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

     
    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",
     
    93599363        if ("EDIT_CUT_POINTS" == type)
    93609364            osd->DialogAddButton(QObject::tr("Cut List Options"),
    93619365                                 "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
     9366        if (ctx->player->DeleteMapHasUndo())
     9367            osd->DialogAddButton(QObject::tr("Undo") + " - " +
     9368                                 ctx->player->DeleteMapGetUndoMessage(),
     9369                                 QString("DIALOG_CUTPOINT_UNDO_0"));
     9370        if (ctx->player->DeleteMapHasRedo())
     9371            osd->DialogAddButton(QObject::tr("Redo") + " - " +
     9372                                 ctx->player->DeleteMapGetRedoMessage(),
     9373                                 QString("DIALOG_CUTPOINT_REDO_0"));
    93629374    }
    93639375    else if ("CUT_LIST_OPTIONS" == type)
    93649376    {
  • 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

     
    33593359void MythPlayer::SetPlayerInfo(TV *tv, QWidget *widget,
    33603360                               bool frame_exact_seek, PlayerContext *ctx)
    33613361{
     3362    deleteMap.SetPlayerContext(ctx);
    33623363    m_tv = tv;
    33633364    parentWidget = widget;
    33643365    exactseeks   = frame_exact_seek;
     
    33843385    deleteMap.SetEditing(true);
    33853386    osd->DialogQuit();
    33863387    osd->HideAll();
     3388
     3389    bool loadedAutoSave = deleteMap.LoadAutoSaveMap(totalFrames, player_ctx);
     3390    (void)loadedAutoSave; // XXX - should we display an OSD message?
     3391
    33873392    deleteMap.UpdateSeekAmount(0, video_frame_rate);
    33883393    deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate,
    33893394                        player_ctx, osd);
     
    34973502        {
    34983503            if (IsInDelete(frame))
    34993504            {
    3500                 deleteMap.Delete(frame, totalFrames);
     3505                deleteMap.Delete(frame, totalFrames,
     3506                                 QObject::tr("Delete cut area"));
    35013507                refresh = true;
    35023508            }
    35033509        }
    35043510        else if (action == "REVERT")
    35053511        {
    3506             deleteMap.LoadMap(totalFrames, player_ctx);
     3512            deleteMap.LoadMap(totalFrames, player_ctx,
     3513                              QObject::tr("Load Cut List"));
    35073514            refresh = true;
    35083515        }
    35093516        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

     
    26802680    return kDisableAutoExpire;
    26812681}
    26822682
    2683 void ProgramInfo::QueryCutList(frm_dir_map_t &delMap) const
     2683bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
    26842684{
    2685     QueryMarkupMap(delMap, MARK_CUT_START);
    2686     QueryMarkupMap(delMap, MARK_CUT_END, true);
    2687     QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
     2685    frm_dir_map_t autosaveMap;
     2686    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
     2687    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
     2688    QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
     2689    bool result = !autosaveMap.isEmpty();
     2690
     2691    if (loadAutoSave)
     2692    {
     2693        // Convert the temporary marks into regular marks.
     2694        delMap.clear();
     2695        frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
     2696        for (; i != autosaveMap.constEnd(); ++i)
     2697        {
     2698            uint64_t frame = i.key();
     2699            MarkTypes mark = i.value();
     2700            if (mark == MARK_TMP_CUT_START)
     2701                mark = MARK_CUT_START;
     2702            else if (mark == MARK_TMP_CUT_END)
     2703                mark = MARK_CUT_END;
     2704            delMap[frame] = mark;
     2705        }
     2706    }
     2707    else
     2708    {
     2709        QueryMarkupMap(delMap, MARK_CUT_START);
     2710        QueryMarkupMap(delMap, MARK_CUT_END, true);
     2711        QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
     2712    }
     2713
     2714    return result;
    26882715}
    26892716
    2690 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap) const
     2717void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
    26912718{
    2692     ClearMarkupMap(MARK_CUT_START);
    2693     ClearMarkupMap(MARK_CUT_END);
     2719    if (!isAutoSave)
     2720    {
     2721        ClearMarkupMap(MARK_CUT_START);
     2722        ClearMarkupMap(MARK_CUT_END);
     2723    }
    26942724    ClearMarkupMap(MARK_PLACEHOLDER);
    2695     SaveMarkupMap(delMap);
     2725    ClearMarkupMap(MARK_TMP_CUT_START);
     2726    ClearMarkupMap(MARK_TMP_CUT_END);
    26962727
     2728    frm_dir_map_t tmpDelMap;
     2729    frm_dir_map_t::const_iterator i = delMap.constBegin();
     2730    for (; i != delMap.constEnd(); ++i)
     2731    {
     2732        uint64_t frame = i.key();
     2733        MarkTypes mark = i.value();
     2734        if (isAutoSave)
     2735        {
     2736            if (mark == MARK_CUT_START)
     2737                mark = MARK_TMP_CUT_START;
     2738            else if (mark == MARK_CUT_END)
     2739                mark = MARK_TMP_CUT_END;
     2740        }
     2741        tmpDelMap[frame] = mark;
     2742    }
     2743    SaveMarkupMap(tmpDelMap);
     2744
    26972745    if (IsRecording())
    26982746    {
    26992747        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,