Ticket #8901: cutlist_undo_stack_v4.patch
File cutlist_undo_stack_v4.patch, 19.6 KB (added by , 14 years ago) |
---|
-
libs/libmythtv/deletemap.h
4 4 #include "programinfo.h" 5 5 #include "playercontext.h" 6 6 7 class DeleteMap; 8 9 typedef 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 7 17 class DeleteMap 8 18 { 9 19 public: 10 20 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(""); } 12 23 24 void SetPlayerContext(PlayerContext *ctx) { m_ctx = ctx; } 13 25 bool HandleAction(QString &action, uint64_t frame, uint64_t played, 14 26 uint64_t total, double rate); 15 27 int GetSeekAmount(void) { return m_seekamount; } … … 27 39 28 40 void SetMap(const frm_dir_map_t &map); 29 41 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); 32 45 void CleanMap(uint64_t total); 33 46 34 void Clear( void);47 void Clear(QString undoMessage=""); 35 48 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); 37 51 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); 39 53 void Reverse(uint64_t frame, uint64_t total); 40 54 void MoveRelative(uint64_t frame, uint64_t total, bool right); 41 55 void Move(uint64_t frame, uint64_t to, uint64_t total); … … 49 63 void TrackerReset(uint64_t frame, uint64_t total); 50 64 bool TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to); 51 65 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 52 73 private: 53 74 void Add(uint64_t frame, MarkTypes type); 54 75 MarkTypes Delete(uint64_t frame); 55 76 77 void Push(QString undoMessage); 78 56 79 bool m_editing; 57 80 uint64_t m_nextCutStart; 58 81 frm_dir_map_t m_deleteMap; … … 60 83 bool m_changed; 61 84 int m_seekamountpos; 62 85 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; 63 91 }; 64 92 65 93 #endif // DELETEMAP_H -
libs/libmythtv/mythplayer.h
301 301 uint64_t GetNearestMark(uint64_t frame, bool right); 302 302 bool IsTemporaryMark(uint64_t frame); 303 303 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(); } 304 308 305 309 // Decoder stuff.. 306 310 VideoFrame *GetNextVideoFrame(bool allow_unsafe = true); -
libs/libmythtv/tv_play.cpp
773 773 "Jump back 10x the normal amount"), ",,<"); 774 774 REG_KEY("TV Editing", "BIGJUMPFWD", QT_TRANSLATE_NOOP("MythControls", 775 775 "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"); 776 780 777 781 /* Teletext keys */ 778 782 REG_KEY("Teletext Menu", "NEXTPAGE", QT_TRANSLATE_NOOP("MythControls", … … 9075 9079 if ("EDIT_CUT_POINTS" == type) 9076 9080 osd->DialogAddButton(QObject::tr("Cut List Options"), 9077 9081 "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")); 9078 9090 } 9079 9091 else if ("CUT_LIST_OPTIONS" == type) 9080 9092 { -
libs/libmythtv/deletemap.cpp
10 10 #define EDIT_CHECK if(!m_editing) \ 11 11 { VERBOSE(VB_IMPORTANT, LOC_ERR + "Cannot edit outside editmode."); return; } 12 12 13 DeleteMapUndoEntry::DeleteMapUndoEntry(frm_dir_map_t dm, QString msg) : 14 deleteMap(dm), message(msg) { } 15 16 DeleteMapUndoEntry::DeleteMapUndoEntry(void) 17 { 18 frm_dir_map_t dm; 19 deleteMap = dm; 20 message = ""; 21 } 22 23 void 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 34 bool 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 45 bool 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 56 QString DeleteMap::GetUndoMessage(void) 57 { 58 return (HasUndo() ? m_undoStack[m_undoStackPointer].message : 59 QObject::tr("(No more undo operations)")); 60 } 61 62 QString DeleteMap::GetRedoMessage(void) 63 { 64 return (HasRedo() ? m_undoStack[m_undoStackPointer + 1].message : 65 QObject::tr("(No more redo operations)")); 66 } 67 13 68 bool DeleteMap::HandleAction(QString &action, uint64_t frame, 14 69 uint64_t played, uint64_t total, double rate) 15 70 { … … 21 76 else if (action == "DOWN") 22 77 UpdateSeekAmount(-1, rate); 23 78 else if (action == "CLEARMAP") 24 Clear( );79 Clear(QObject::tr("Clear Cut List")); 25 80 else if (action == "INVERTMAP") 26 81 ReverseAll(total); 27 82 else if (action == "MOVEPREV") … … 29 84 else if (action == "MOVENEXT") 30 85 MoveRelative(frame, total, true); 31 86 else if (action == "CUTTOBEGINNING") 32 Add(frame, total, MARK_CUT_END );87 Add(frame, total, MARK_CUT_END, QObject::tr("Cut To Beginning")); 33 88 else if (action == "CUTTOEND") 34 Add(frame, total, MARK_CUT_START );89 Add(frame, total, MARK_CUT_START, QObject::tr("Cut To End")); 35 90 else if (action == "NEWCUT") 36 91 NewCut(frame, total); 37 92 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(); 39 98 else 40 99 handled = false; 41 100 return handled; … … 150 209 } 151 210 152 211 /// Clears the deleteMap. 153 void DeleteMap::Clear( void)212 void DeleteMap::Clear(QString undoMessage) 154 213 { 155 214 m_deleteMap.clear(); 156 215 m_changed = true; 216 if (!undoMessage.isEmpty()) 217 Push(undoMessage); 157 218 } 158 219 159 220 /// Reverses the direction of each mark in the map. … … 165 226 Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START : 166 227 MARK_CUT_END); 167 228 CleanMap(total); 229 Push(QObject::tr("Invert Cut List")); 168 230 } 169 231 170 232 /** … … 172 234 * existing redundant mark of that type is removed. This simplifies 173 235 * the cleanup code. 174 236 */ 175 void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type) 237 void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type, 238 QString undoMessage) 176 239 { 177 240 EDIT_CHECK 178 241 if ((MARK_CUT_START != type) && (MARK_CUT_END != type) && … … 186 249 { 187 250 // Delete the temporary mark before putting a real mark at its 188 251 // location 189 Delete(frame, total );252 Delete(frame, total, ""); 190 253 } 191 254 else // Don't add a mark on top of a mark 192 255 return; … … 241 304 Delete((uint64_t)remove); 242 305 Add(frame, type); 243 306 CleanMap(total); 307 if (!undoMessage.isEmpty()) 308 Push(undoMessage); 244 309 } 245 310 246 311 /// Remove the mark at the given frame. 247 void DeleteMap::Delete(uint64_t frame, uint64_t total )312 void DeleteMap::Delete(uint64_t frame, uint64_t total, QString undoMessage) 248 313 { 249 314 EDIT_CHECK 250 315 if (m_deleteMap.isEmpty()) … … 271 336 if (prev != next) 272 337 Delete(next); 273 338 CleanMap(total); 339 if (!undoMessage.isEmpty()) 340 Push(undoMessage); 274 341 } 275 342 276 343 /// Reverse the direction of the mark at the given frame. … … 278 345 { 279 346 EDIT_CHECK 280 347 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")); 282 350 } 283 351 284 352 /// Add a new cut marker (to start or end a cut region) … … 376 444 Add(frame, MARK_PLACEHOLDER); 377 445 378 446 CleanMap(total); 447 Push(QObject::tr("New Cut")); 379 448 } 380 449 381 450 /// Move the previous (!right) or next (right) cut to frame. … … 397 466 { 398 467 // If on a mark, don't collapse a cut region to 0; 399 468 // instead, delete the region 400 Delete(frame, total );469 Delete(frame, total, QObject::tr("Delete Cut Area")); 401 470 return; 402 471 } 403 472 else if (MARK_PLACEHOLDER == type) 404 473 { 405 474 // Delete the temporary mark before putting a real mark at its 406 475 // location 407 Delete(frame, total );476 Delete(frame, total, ""); 408 477 } 409 478 } 410 479 … … 424 493 else if (frame == total) 425 494 type = MARK_CUT_END; 426 495 } 427 Add(to, total, type );496 Add(to, total, type, QObject::tr("Move Mark")); 428 497 } 429 498 430 499 /// Private addition to the deleteMap. … … 582 651 Clear(); 583 652 m_deleteMap = map; 584 653 m_deleteMap.detach(); 654 Push(QObject::tr("Set New Cut List")); 585 655 } 586 656 587 657 /// Loads the given commercial break map into the deleteMap. … … 593 663 Add(it.key(), it.value() == MARK_COMM_START ? 594 664 MARK_CUT_START : MARK_CUT_END); 595 665 CleanMap(total); 666 Push(QObject::tr("Load Commskip List")); 596 667 } 597 668 598 669 /// Loads the delete map from the database. 599 void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx )670 void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage) 600 671 { 601 672 if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored()) 602 673 return; … … 606 677 ctx->playingInfo->QueryCutList(m_deleteMap); 607 678 ctx->UnlockPlayingInfo(__FILE__, __LINE__); 608 679 CleanMap(total); 680 if (!undoMessage.isEmpty()) 681 Push(undoMessage); 609 682 } 610 683 684 /// Returns true if an auto-save map was loaded. 685 /// Does nothing and returns false if not. 686 bool 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 611 705 /// Saves the delete map to the database. 612 void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx )706 void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave) 613 707 { 614 708 if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored()) 615 709 return; 616 710 711 if (!isAutoSave) 712 { 617 713 // Remove temporary placeholder marks 618 714 QMutableMapIterator<uint64_t, MarkTypes> it(m_deleteMap); 619 715 while (it.hasNext()) … … 627 723 } 628 724 629 725 CleanMap(total); 726 } 630 727 ctx->LockPlayingInfo(__FILE__, __LINE__); 631 728 ctx->playingInfo->SaveMarkupFlag(MARK_UPDATED_CUT); 632 ctx->playingInfo->SaveCutList(m_deleteMap );729 ctx->playingInfo->SaveCutList(m_deleteMap, isAutoSave); 633 730 ctx->UnlockPlayingInfo(__FILE__, __LINE__); 634 731 } 635 732 -
libs/libmythtv/mythplayer.cpp
3387 3387 void MythPlayer::SetPlayerInfo(TV *tv, QWidget *widget, 3388 3388 bool frame_exact_seek, PlayerContext *ctx) 3389 3389 { 3390 deleteMap.SetPlayerContext(ctx); 3390 3391 m_tv = tv; 3391 3392 parentWidget = widget; 3392 3393 exactseeks = frame_exact_seek; … … 3412 3413 deleteMap.SetEditing(true); 3413 3414 osd->DialogQuit(); 3414 3415 osd->HideAll(); 3416 3417 bool loadedAutoSave = deleteMap.LoadAutoSaveMap(totalFrames, player_ctx); 3418 (void)loadedAutoSave; // XXX - should we display an OSD message? 3419 3415 3420 deleteMap.UpdateSeekAmount(0, video_frame_rate); 3416 3421 deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate, 3417 3422 player_ctx, osd); … … 3426 3431 void MythPlayer::DisableEdit(bool save) 3427 3432 { 3428 3433 deleteMap.SetEditing(false, osd); 3429 if (save) 3430 deleteMap.SaveMap(totalFrames, player_ctx); 3431 else 3434 if (!save) 3432 3435 deleteMap.LoadMap(totalFrames, player_ctx); 3436 // Unconditionally save to remove temporary marks from the DB. 3437 deleteMap.SaveMap(totalFrames, player_ctx); 3433 3438 deleteMap.TrackerReset(framesPlayed, totalFrames); 3434 3439 deleteMap.SetFileEditing(player_ctx, false); 3435 3440 player_ctx->LockPlayingInfo(__FILE__, __LINE__); … … 3525 3530 { 3526 3531 if (IsInDelete(frame)) 3527 3532 { 3528 deleteMap.Delete(frame, totalFrames); 3533 deleteMap.Delete(frame, totalFrames, 3534 QObject::tr("Delete cut area")); 3529 3535 refresh = true; 3530 3536 } 3531 3537 } 3532 3538 else if (action == "REVERT") 3533 3539 { 3534 deleteMap.LoadMap(totalFrames, player_ctx); 3540 deleteMap.LoadMap(totalFrames, player_ctx, 3541 QObject::tr("Load Cut List")); 3535 3542 refresh = true; 3536 3543 } 3537 3544 else if (action == "REVERTEXIT") -
libs/libmyth/programtypes.cpp
22 22 switch (type) 23 23 { 24 24 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"; 25 27 case MARK_UPDATED_CUT: return "UPDATED_CUT"; 26 28 case MARK_PLACEHOLDER: return "PLACEHOLDER"; 27 29 case MARK_CUT_END: return "CUT_END"; -
libs/libmyth/programinfo.h
533 533 bool forceCheckLocal = false) const; 534 534 535 535 // 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; 538 538 539 539 // Commercial flagging map 540 540 void QueryCommBreakList(frm_dir_map_t &) const; -
libs/libmyth/programinfo.cpp
2685 2685 return kDisableAutoExpire; 2686 2686 } 2687 2687 2688 void ProgramInfo::QueryCutList(frm_dir_map_t &delMap) const2688 bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const 2689 2689 { 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; 2693 2720 } 2694 2721 2695 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap ) const2722 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const 2696 2723 { 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 } 2699 2729 ClearMarkupMap(MARK_PLACEHOLDER); 2700 SaveMarkupMap(delMap); 2730 ClearMarkupMap(MARK_TMP_CUT_START); 2731 ClearMarkupMap(MARK_TMP_CUT_END); 2701 2732 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 2702 2750 if (IsRecording()) 2703 2751 { 2704 2752 MSqlQuery query(MSqlQuery::InitCon()); -
libs/libmyth/programtypes.h
40 40 typedef enum { 41 41 MARK_ALL = -100, 42 42 MARK_UNSET = -10, 43 MARK_TMP_CUT_END = -5, 44 MARK_TMP_CUT_START = -4, 43 45 MARK_UPDATED_CUT = -3, 44 46 MARK_PLACEHOLDER = -2, 45 47 MARK_CUT_END = 0,