Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Editor: Add Undo/Redo and auto-save-to-temporary list.
Adds an undo/redo stack, and saves changes to a temporary list as you
work.

Key bindings will be modified in future commits, so they're commented
from Jim's patch.

Thanks to Jim Stichnoth for the wonderful new feature.
  • Loading branch information
sphery committed Jan 28, 2011
1 parent 28d371e commit 0791662
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 35 deletions.
63 changes: 56 additions & 7 deletions mythtv/libs/libmyth/programinfo.cpp
Expand Up @@ -2695,18 +2695,67 @@ AutoExpireType ProgramInfo::QueryAutoExpire(void) const
return kDisableAutoExpire;
}

void ProgramInfo::QueryCutList(frm_dir_map_t &delMap) const
bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
{
QueryMarkupMap(delMap, MARK_CUT_START);
QueryMarkupMap(delMap, MARK_CUT_END, true);
frm_dir_map_t autosaveMap;
QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
bool result = !autosaveMap.isEmpty();

if (loadAutoSave)
{
// Convert the temporary marks into regular marks.
delMap.clear();
frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
for (; i != autosaveMap.constEnd(); ++i)
{
uint64_t frame = i.key();
MarkTypes mark = i.value();
if (mark == MARK_TMP_CUT_START)
mark = MARK_CUT_START;
else if (mark == MARK_TMP_CUT_END)
mark = MARK_CUT_END;
delMap[frame] = mark;
}
}
else
{
QueryMarkupMap(delMap, MARK_CUT_START);
QueryMarkupMap(delMap, MARK_CUT_END, true);
QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
}

return result;
}

void ProgramInfo::SaveCutList(frm_dir_map_t &delMap) const
void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
{
ClearMarkupMap(MARK_CUT_START);
ClearMarkupMap(MARK_CUT_END);
if (!isAutoSave)
{
ClearMarkupMap(MARK_CUT_START);
ClearMarkupMap(MARK_CUT_END);
}
ClearMarkupMap(MARK_PLACEHOLDER);
SaveMarkupMap(delMap);
ClearMarkupMap(MARK_TMP_CUT_START);
ClearMarkupMap(MARK_TMP_CUT_END);

frm_dir_map_t tmpDelMap;
frm_dir_map_t::const_iterator i = delMap.constBegin();
for (; i != delMap.constEnd(); ++i)
{
uint64_t frame = i.key();
MarkTypes mark = i.value();
if (isAutoSave)
{
if (mark == MARK_CUT_START)
mark = MARK_TMP_CUT_START;
else if (mark == MARK_CUT_END)
mark = MARK_TMP_CUT_END;
}
tmpDelMap[frame] = mark;
}
SaveMarkupMap(tmpDelMap);

if (IsRecording())
{
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmyth/programinfo.h
Expand Up @@ -534,8 +534,8 @@ class MPUBLIC ProgramInfo
bool forceCheckLocal = false) const;

// Edit flagging map
void QueryCutList(frm_dir_map_t &) const;
void SaveCutList(frm_dir_map_t &) const;
bool QueryCutList(frm_dir_map_t &, bool loadAutosave=false) const;
void SaveCutList(frm_dir_map_t &, bool isAutoSave=false) const;

// Commercial flagging map
void QueryCommBreakList(frm_dir_map_t &) const;
Expand Down
2 changes: 2 additions & 0 deletions mythtv/libs/libmyth/programtypes.cpp
Expand Up @@ -22,6 +22,8 @@ QString toString(MarkTypes type)
switch (type)
{
case MARK_UNSET: return "UNSET";
case MARK_TMP_CUT_END: return "TMP_CUT_END";
case MARK_TMP_CUT_START:return "TMP_CUT_START";
case MARK_UPDATED_CUT: return "UPDATED_CUT";
case MARK_PLACEHOLDER: return "PLACEHOLDER";
case MARK_CUT_END: return "CUT_END";
Expand Down
2 changes: 2 additions & 0 deletions mythtv/libs/libmyth/programtypes.h
Expand Up @@ -40,6 +40,8 @@ typedef QMap<uint64_t, uint64_t> frm_pos_map_t;
typedef enum {
MARK_ALL = -100,
MARK_UNSET = -10,
MARK_TMP_CUT_END = -5,
MARK_TMP_CUT_START = -4,
MARK_UPDATED_CUT = -3,
MARK_PLACEHOLDER = -2,
MARK_CUT_END = 0,
Expand Down
127 changes: 112 additions & 15 deletions mythtv/libs/libmythtv/deletemap.cpp
Expand Up @@ -10,6 +10,61 @@
#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 ++;
SaveMap(0, m_ctx, true);
}

bool DeleteMap::Undo(void)
{
if (!HasUndo())
return false;
m_undoStackPointer --;
m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
m_changed = true;
SaveMap(0, m_ctx, true);
return true;
}

bool DeleteMap::Redo(void)
{
if (!HasRedo())
return false;
m_undoStackPointer ++;
m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
m_changed = true;
SaveMap(0, m_ctx, 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)
{
Expand All @@ -21,21 +76,25 @@ bool DeleteMap::HandleAction(QString &action, uint64_t frame,
else if (action == ACTION_DOWN)
UpdateSeekAmount(-1, rate);
else if (action == ACTION_CLEARMAP)
Clear();
Clear(QObject::tr("Clear Cut List"));
else if (action == ACTION_INVERTMAP)
ReverseAll(total);
else if (action == "MOVEPREV")
MoveRelative(frame, total, false);
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;
Expand Down Expand Up @@ -150,10 +209,12 @@ bool DeleteMap::IsEmpty(void)
}

/// Clears the deleteMap.
void DeleteMap::Clear(void)
void DeleteMap::Clear(QString undoMessage)
{
m_deleteMap.clear();
m_changed = true;
if (!undoMessage.isEmpty())
Push(undoMessage);
}

/// Reverses the direction of each mark in the map.
Expand All @@ -165,14 +226,16 @@ void DeleteMap::ReverseAll(uint64_t total)
Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
MARK_CUT_END);
CleanMap(total);
Push(QObject::tr("Invert Cut List"));
}

/**
* \brief Add a new mark of the given type. Before the new mark is added, any
* 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) &&
Expand All @@ -186,7 +249,7 @@ void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
{
// 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;
Expand Down Expand Up @@ -241,10 +304,12 @@ void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
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())
Expand All @@ -271,14 +336,17 @@ void DeleteMap::Delete(uint64_t frame, uint64_t total)
if (prev != next)
Delete(next);
CleanMap(total);
if (!undoMessage.isEmpty())
Push(undoMessage);
}

/// Reverse the direction of the mark at the given frame.
void DeleteMap::Reverse(uint64_t frame, uint64_t total)
{
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)
Expand Down Expand Up @@ -376,6 +444,7 @@ void DeleteMap::NewCut(uint64_t frame, uint64_t total)
Add(frame, MARK_PLACEHOLDER);

CleanMap(total);
Push(QObject::tr("New Cut"));
}

/// Move the previous (!right) or next (right) cut to frame.
Expand All @@ -397,14 +466,14 @@ void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right)
{
// 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, "");
}
}

Expand All @@ -424,7 +493,7 @@ void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total)
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.
Expand Down Expand Up @@ -586,6 +655,7 @@ void DeleteMap::SetMap(const frm_dir_map_t &map)
Clear();
m_deleteMap = map;
m_deleteMap.detach();
Push(QObject::tr("Set New Cut List"));
}

/// Loads the given commercial break map into the deleteMap.
Expand All @@ -597,10 +667,11 @@ void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map)
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.
void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage)
{
if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return;
Expand All @@ -610,14 +681,39 @@ void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
ctx->playingInfo->QueryCutList(m_deleteMap);
ctx->UnlockPlayingInfo(__FILE__, __LINE__);
CleanMap(total);
if (!undoMessage.isEmpty())
Push(undoMessage);
}

/// Returns true if an auto-save map was loaded.
/// Does nothing and returns false if not.
bool DeleteMap::LoadAutoSaveMap(uint64_t total, PlayerContext *ctx)
{
if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return false;

frm_dir_map_t tmpDeleteMap = m_deleteMap;
Clear();
ctx->LockPlayingInfo(__FILE__, __LINE__);
bool result = ctx->playingInfo->QueryCutList(m_deleteMap, true);
ctx->UnlockPlayingInfo(__FILE__, __LINE__);
CleanMap(total);
if (result)
Push(QObject::tr("Load Auto-Save Cut List"));
else
m_deleteMap = tmpDeleteMap;

return result;
}

/// Saves the delete map to the database.
void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx)
void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave)
{
if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return;

if (!isAutoSave)
{
// Remove temporary placeholder marks
QMutableMapIterator<uint64_t, MarkTypes> it(m_deleteMap);
while (it.hasNext())
Expand All @@ -631,9 +727,10 @@ void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx)
}

CleanMap(total);
}
ctx->LockPlayingInfo(__FILE__, __LINE__);
ctx->playingInfo->SaveMarkupFlag(MARK_UPDATED_CUT);
ctx->playingInfo->SaveCutList(m_deleteMap);
ctx->playingInfo->SaveCutList(m_deleteMap, isAutoSave);
ctx->UnlockPlayingInfo(__FILE__, __LINE__);
}

Expand Down

0 comments on commit 0791662

Please sign in to comment.