From fd28f4c2b0496716c6fcb50d5d887de4e9c02113 Mon Sep 17 00:00:00 2001 From: ansstuff Date: Sun, 9 Oct 2011 16:33:39 +0000 Subject: [PATCH] * Tasedit: no more slow seeking and no NewProject in File menu * Tasedit: input history, undo (Ctrl-Z), redo (Ctrl-Y) * Tasedit: greenzone reduces only from the point with actual differences were found * Tasedit: history ListView, jumping to any position * Tasedit: Config->Set max Undo levels (1-999, 100) * Tasedit: View->Jump when making undo, undo hint line * Tasedit: saving/loading history to .tas file --- src/drivers/win/config.cpp | 4 + src/drivers/win/res.rc | 32 +- src/drivers/win/resource.h | 14 +- src/drivers/win/tasedit.cpp | 575 ++++++++++++------- src/drivers/win/tasedit.h | 43 +- src/drivers/win/taseditlib/inputhistory.cpp | 600 ++++++++++++++++++++ src/drivers/win/taseditlib/inputhistory.h | 102 ++++ src/drivers/win/taseditlib/taseditproj.cpp | 20 +- src/drivers/win/taseditlib/taseditproj.h | 11 +- src/movie.cpp | 25 +- src/movie.h | 3 +- vc/vc10_fceux.vcxproj | 3 + vc/vc10_fceux.vcxproj.filters | 3 + 13 files changed, 1198 insertions(+), 237 deletions(-) create mode 100644 src/drivers/win/taseditlib/inputhistory.cpp create mode 100644 src/drivers/win/taseditlib/inputhistory.h diff --git a/src/drivers/win/config.cpp b/src/drivers/win/config.cpp index 875ba61c..a47c6013 100644 --- a/src/drivers/win/config.cpp +++ b/src/drivers/win/config.cpp @@ -75,6 +75,8 @@ extern bool TASEdit_bind_markers; extern bool TASEdit_restore_position; extern bool TASEdit_show_dot; extern int TASEdit_greenzone_capacity; +extern int TasEdit_undo_levels; +extern bool TASEdit_jump_to_undo; //window positions and sizes: extern int ChtPosX,ChtPosY; @@ -299,6 +301,8 @@ static CFGSTRUCT fceuconfig[] = { AC(TASEdit_restore_position), AC(TASEdit_show_dot), AC(TASEdit_greenzone_capacity), + AC(TasEdit_undo_levels), + AC(TASEdit_jump_to_undo), AC(lagCounterDisplay), AC(oldInputDisplay), AC(movieSubtitles), diff --git a/src/drivers/win/res.rc b/src/drivers/win/res.rc index 54beee84..7a1ae125 100644 --- a/src/drivers/win/res.rc +++ b/src/drivers/win/res.rc @@ -219,7 +219,6 @@ TASEDITMENU MENU BEGIN POPUP "&File" BEGIN - MENUITEM "&New Project", ID_FILE_NEWPROJECT MENUITEM "&Open Project...", ID_FILE_OPENPROJECT MENUITEM "&Save Project\tCtrl+S", ID_FILE_SAVEPROJECT MENUITEM "S&ave Project As...", ID_FILE_SAVEPROJECTAS @@ -245,17 +244,24 @@ BEGIN MENUITEM "Insert # of Frames\tInsert", ID_EDIT_INSERT MENUITEM "Cl&one\tShift+Ins", ID_EDIT_CLONEFRAMES MENUITEM SEPARATOR - MENUITEM "T&runcate movie\tCtrl+T", ID_EDIT_TRUNCATE + MENUITEM "Truncate movie\tCtrl+T", ID_EDIT_TRUNCATE + MENUITEM SEPARATOR + MENUITEM "&Undo\tCtrl-Z", ID_EDIT_UNDO + MENUITEM "&Redo\tCtrl-Y", ID_EDIT_REDO END POPUP "&View" BEGIN MENUITEM "Highlight &Lag Frames", ID_VIEW_SHOW_LAG_FRAMES MENUITEM "Show &Markers", ID_VIEW_SHOW_MARKERS MENUITEM "Show &dot in empty cells", ID_VIEW_SHOWDOTINEMPTYCELLS + MENUITEM SEPARATOR + MENUITEM "&Follow undo context", ID_VIEW_JUMPWHENMAKINGUNDO END POPUP "&Config" BEGIN MENUITEM "Set &greenzone capacity", ID_CONFIG_SETGREENZONECAPACITY + MENUITEM "Set max undo levels", ID_CONFIG_SETMAXUNDOLEVELS + MENUITEM SEPARATOR MENUITEM "Mute &Turbo", ID_CONFIG_MUTETURBO MENUITEM "&Bind Markers to Input", ID_CONFIG_BINDMARKERSTOINPUT END @@ -341,7 +347,7 @@ BEGIN BEGIN MENUITEM "Insert # of Frames", MENU_CONTEXT_STRAY_INSERTFRAMES MENUITEM SEPARATOR - MENUITEM "T&runcate movie", ID_CONTEXT_STRAY_TRUNCATE + MENUITEM "Truncate movie", ID_CONTEXT_STRAY_TRUNCATE END POPUP "Selected" BEGIN @@ -353,7 +359,7 @@ BEGIN MENUITEM "Insert # of Frames", ID_CONTEXT_SELECTED_INSERTFRAMES2 MENUITEM "Cl&one", ID_SELECTED_CLONE MENUITEM SEPARATOR - MENUITEM "T&runcate movie", ID_CONTEXT_SELECTED_TRUNCATE + MENUITEM "Truncate movie", ID_CONTEXT_SELECTED_TRUNCATE END END @@ -1348,8 +1354,8 @@ CAPTION "TAS Editor" MENU TASEDITMENU FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_BORDER,6,5,298,370 - GROUPBOX "Playback control",IDC_STATIC,310,5,118,62,BS_CENTER,WS_EX_RIGHT + CONTROL "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_BORDER,6,5,299,370 + GROUPBOX "Playback",IDC_STATIC,310,5,118,62,BS_CENTER,WS_EX_RIGHT PUSHBUTTON "<<",TASEDIT_REWIND_FULL,314,14,22,14,NOT WS_TABSTOP PUSHBUTTON "<",TASEDIT_REWIND,336,14,22,14,NOT WS_TABSTOP PUSHBUTTON "||",TASEDIT_PLAYSTOP,358,14,22,14,NOT WS_TABSTOP @@ -1360,12 +1366,11 @@ BEGIN CONTROL " Follow cursor",CHECK_FOLLOW_CURSOR,"Button",BS_AUTOCHECKBOX,315,30,105,12 CONTROL " Auto-restore last position",CHECK_AUTORESTORE_PLAYBACK, "Button",BS_AUTOCHECKBOX,315,53,105,12 - GROUPBOX "Recording input",IDC_STATIC,310,68,118,48,BS_CENTER,WS_EX_RIGHT - GROUPBOX "Editing",IDC_STATIC,310,118,118,29,BS_CENTER,WS_EX_RIGHT - GROUPBOX "Bookmarks",IDC_STATIC,310,148,118,103,BS_CENTER,WS_EX_RIGHT - CONTROL "",IDC_LIST3,"SysListView32",LVS_LIST | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSCROLL | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,159,108,88 - GROUPBOX "Project Input Logs",IDC_STATIC,310,252,118,123,BS_CENTER,WS_EX_RIGHT - CONTROL "",IDC_LIST2,"SysListView32",LVS_LIST | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,263,108,108,WS_EX_LEFTSCROLLBAR + GROUPBOX "Recording",IDC_STATIC,310,68,118,48,BS_CENTER,WS_EX_RIGHT + GROUPBOX "Editing",IDC_STATIC,310,118,118,38,BS_CENTER,WS_EX_RIGHT + GROUPBOX "Bookmarks",IDC_STATIC,310,158,118,101,BS_CENTER,WS_EX_RIGHT + CONTROL "",IDC_BOOKMARKSLIST,"SysListView32",LVS_LIST | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSCROLL | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,168,108,86 + CONTROL "",IDC_HISTORYLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOLABELWRAP | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,271,108,100 CONTROL " OFF",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,316,78,29,10 CONTROL " ON",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,316,91,29,10 CONTROL " 1P",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,368,78,25,10 @@ -1374,6 +1379,7 @@ BEGIN CONTROL " 4P",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,397,91,23,10 CONTROL " Superimpose",IDC_SUPERIMPOSE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,316,104,55,10 CONTROL " Omit blank",IDC_OMITBLANK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,375,104,49,10 + GROUPBOX "History",IDC_STATIC,310,261,118,114,BS_CENTER,WS_EX_RIGHT END ASSEMBLER DIALOGEX 0, 0, 202, 135 @@ -2026,6 +2032,8 @@ BEGIN "T", ACCEL_CTRL_T, VIRTKEY, CONTROL, NOINVERT "V", ACCEL_CTRL_V, VIRTKEY, CONTROL, NOINVERT "X", ACCEL_CTRL_X, VIRTKEY, CONTROL, NOINVERT + "Y", ACCEL_CTRL_Y, VIRTKEY, CONTROL, NOINVERT + "Z", ACCEL_CTRL_Z, VIRTKEY, CONTROL, NOINVERT VK_DELETE, ACCEL_DEL, VIRTKEY, NOINVERT VK_INSERT, ACCEL_INS, VIRTKEY, NOINVERT VK_INSERT, ACCEL_SHIFT_INS, VIRTKEY, SHIFT, NOINVERT diff --git a/src/drivers/win/resource.h b/src/drivers/win/resource.h index 4f468474..5f5e243f 100644 --- a/src/drivers/win/resource.h +++ b/src/drivers/win/resource.h @@ -396,8 +396,8 @@ #define IDC_BUTTON8 1146 #define IDC_EDIT1 1147 #define IDC_BUTTON9 1148 -#define IDC_LIST2 1149 -#define IDC_LIST3 1150 +#define IDC_HISTORYLIST 1149 +#define IDC_BOOKMARKSLIST 1150 #define CHECK_SOUND_MUTETURBO 1179 #define IDC_EDIT_AUTHOR 1180 #define MEMW_STATIC 1181 @@ -851,6 +851,14 @@ #define ID_EDIT_CLONEFRAMES 40460 #define ACCEL_SHIFT_INS 40461 #define ID_SELECTED_CLONE 40463 +#define ID_CONFIG_Q 40464 +#define ACCEL_CTRL_Z 40465 +#define ACCEL_CTRL_Y 40466 +#define ID_EDIT_UNDO 40468 +#define ID_EDIT_REDO 40469 +#define ID_CONFIG_SETMAXUNDOLEVELS 40470 +#define ID_VIEW_X 40471 +#define ID_VIEW_JUMPWHENMAKINGUNDO 40472 #define IDC_DEBUGGER_ICONTRAY 55535 #define MW_ValueLabel2 65423 #define MW_ValueLabel1 65426 @@ -860,7 +868,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 160 -#define _APS_NEXT_COMMAND_VALUE 40464 +#define _APS_NEXT_COMMAND_VALUE 40473 #define _APS_NEXT_CONTROL_VALUE 1263 #define _APS_NEXT_SYMED_VALUE 101 #endif diff --git a/src/drivers/win/tasedit.cpp b/src/drivers/win/tasedit.cpp index 9d159ebc..a007bf07 100644 --- a/src/drivers/win/tasedit.cpp +++ b/src/drivers/win/tasedit.cpp @@ -1,22 +1,21 @@ #include #include #include -#include #include "common.h" -#include "tasedit.h" #include "taseditlib/taseditproj.h" +//#include "taseditlib/inputhistory.h" #include "fceu.h" #include "debugger.h" #include "replay.h" -#include "movie.h" #include "utils/xstring.h" #include "Win32InputBox.h" #include "window.h" #include "keyboard.h" #include "joystick.h" #include "help.h" -#include "main.h" //For the GetRomName() function +#include "main.h" +#include "tasedit.h" using namespace std; @@ -30,12 +29,14 @@ int lastCursor; bool old_emu_paused, emu_paused; int old_pauseframe; bool old_show_pauseframe, show_pauseframe; +int undo_hint_pos, old_undo_hint_pos, undo_hint_time; +bool old_show_undo_hint, show_undo_hint; bool old_rewind_button_state, rewind_button_state; bool old_forward_button_state, forward_button_state; int button_hold_time; int seeking_start_frame = 0; bool TASEdit_focus = false; -int listItems; // number of items in list +int listItems; // number of items per list page // saved FCEU config int saved_eoptions; int saved_EnableAutosave; @@ -50,9 +51,11 @@ bool TASEdit_show_lag_frames = true; bool TASEdit_show_markers = true; bool TASEdit_bind_markers = true; bool TASEdit_restore_position = false; -int TASEdit_greenzone_capacity = GREENZONE_DEFAULT_CAPACITY; +int TASEdit_greenzone_capacity = GREENZONE_CAPACITY_DEFAULT; extern bool muteTurbo; bool TASEdit_show_dot = true; +int TasEdit_undo_levels = UNDO_LEVELS_DEFAULT; +bool TASEdit_jump_to_undo = true; string tasedithelp = "{16CDE0C4-02B0-4A60-A88D-076319909A4D}"; //Name of TASEdit Help page char buttonNames[NUM_JOYPAD_BUTTONS][2] = {"A", "B", "S", "T", "U", "D", "L", "R"}; @@ -63,10 +66,16 @@ char windowCaptions[6][30] = { "TAS Editor", "TAS Editor (Recording 3P)", "TAS Editor (Recording 4P)"}; HWND hwndTasEdit = 0; -static HMENU hmenu, hrmenu; -static HWND hwndList, hwndHeader, hwndProgressbar, hwndRewind, hwndForward; -static HWND hwndRB_RecOff, hwndRB_RecAll, hwndRB_Rec1P, hwndRB_Rec2P, hwndRB_Rec3P, hwndRB_Rec4P; -static WNDPROC hwndHeader_oldWndproc, hwndList_oldWndProc; +HMENU hmenu, hrmenu; +HWND hwndList, hwndHeader; +WNDPROC hwndList_oldWndProc, hwndHeader_oldWndproc; +HWND hwndHistoryList; +WNDPROC hwndHistoryList_oldWndProc; +HWND hwndBookmarksList; +WNDPROC hwndBookmarksList_oldWndProc; +HWND hwndProgressbar, hwndRewind, hwndForward; +HWND hwndRB_RecOff, hwndRB_RecAll, hwndRB_Rec1P, hwndRB_Rec2P, hwndRB_Rec3P, hwndRB_Rec4P; + typedef std::set TSelectionFrames; static TSelectionFrames selectionFrames; @@ -76,8 +85,11 @@ static TSelectionFrames selectionFrames; extern EMOVIEMODE movieMode; TASEDIT_PROJECT project; +INPUT_HISTORY history; +//GREENZONE greenzone; -static void GetDispInfo(NMLVDISPINFO* nmlvDispInfo) + +void GetDispInfo(NMLVDISPINFO* nmlvDispInfo) { LVITEM& item = nmlvDispInfo->item; if(item.mask & LVIF_TEXT) @@ -131,10 +143,8 @@ static void GetDispInfo(NMLVDISPINFO* nmlvDispInfo) #define CDDS_SUBITEMPREERASE (CDDS_SUBITEM | CDDS_ITEMPREERASE) #define CDDS_SUBITEMPOSTERASE (CDDS_SUBITEM | CDDS_ITEMPOSTERASE) -static LONG CustomDraw(NMLVCUSTOMDRAW* msg) +LONG CustomDraw(NMLVCUSTOMDRAW* msg) { - //LPNMCUSTOMDRAW - int cell_x, cell_y; switch(msg->nmcd.dwDrawStage) { @@ -154,7 +164,11 @@ static LONG CustomDraw(NMLVCUSTOMDRAW* msg) if(cell_x == COLUMN_FRAMENUM || cell_x == COLUMN_FRAMENUM2) { // frame number - if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) + if (show_undo_hint && cell_y == undo_hint_pos) + { + // undo hint here + msg->clrTextBk = UNDOHINT_FRAMENUM_COLOR; + } else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) { // current frame if(TASEdit_show_markers && currMovieData.frames_flags[cell_y] & MARKER_FLAG_BIT) @@ -181,7 +195,11 @@ static LONG CustomDraw(NMLVCUSTOMDRAW* msg) } else if((cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 0 || (cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 2) { // pad 1 or 3 - if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) + if (show_undo_hint && cell_y == undo_hint_pos) + { + // undo hint here + msg->clrTextBk = UNDOHINT_INPUT_COLOR1; + } else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) { // current frame msg->clrTextBk = CUR_INPUT_COLOR1; @@ -200,7 +218,11 @@ static LONG CustomDraw(NMLVCUSTOMDRAW* msg) } else if((cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 1 || (cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 3) { // pad 2 or 4 - if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) + if (show_undo_hint && cell_y == undo_hint_pos) + { + // undo hint here + msg->clrTextBk = UNDOHINT_INPUT_COLOR2; + } else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe)) { // current frame msg->clrTextBk = CUR_INPUT_COLOR2; @@ -228,6 +250,8 @@ static LONG CustomDraw(NMLVCUSTOMDRAW* msg) void UpdateTasEdit() { if(!hwndTasEdit) return; + + UpdateList(); // pause when seeking hit pauseframe if(!FCEUI_EmulationPaused()) @@ -250,6 +274,7 @@ void UpdateTasEdit() // externally paused - progressbar should be full SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH, 0); } + // update flashing pauseframe if (old_pauseframe != pauseframe && old_pauseframe) RedrawRow(old_pauseframe-1); old_pauseframe = pauseframe; @@ -260,10 +285,21 @@ void UpdateTasEdit() show_pauseframe = false; if (old_show_pauseframe != show_pauseframe) RedrawRow(pauseframe-1); - UpdateList(); - //UpdateRecordingRadioButtons(); + // update undo_hint + if (old_undo_hint_pos != undo_hint_pos && old_undo_hint_pos >= 0) RedrawRow(old_undo_hint_pos); + old_undo_hint_pos = undo_hint_pos; + old_show_undo_hint = show_undo_hint; + show_undo_hint = false; + if (undo_hint_pos >= 0) + { + if ((int)clock() < undo_hint_time) + show_undo_hint = true; + else + undo_hint_pos = -1; // finished hinting + } + if (old_show_undo_hint != show_undo_hint) RedrawRow(undo_hint_pos); - //update the cursor + //update the playback cursor if(currFrameCounter != lastCursor) { FollowPlayback(); @@ -323,15 +359,29 @@ void UpdateList() { //update the number of items in the list int currLVItemCount = ListView_GetItemCount(hwndList); - if(currMovieData.getNumRecords() != currLVItemCount) + int movie_size = currMovieData.getNumRecords(); + if(currLVItemCount != movie_size) { - ListView_SetItemCountEx(hwndList,currMovieData.getNumRecords(),LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL); + ListView_SetItemCountEx(hwndList,movie_size,LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL); + // also reduce selection if needed + if (selectionFrames.size()) + { + int delete_index; + while(1) + { + delete_index = *selectionFrames.rbegin(); + if (delete_index < movie_size) break; + // reduce selection manually, because reduced list won't call ItemChanged for these rows + selectionFrames.erase(delete_index); + if (!selectionFrames.size()) break; + } + } } } -void UpdateProgressbar(int frame) +void UpdateProgressbar(int a, int b) { - SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * frame / currMovieData.greenZoneCount, 0); + SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * a / b, 0); } void RedrawWindowCaption() @@ -361,6 +411,12 @@ void RedrawList() { InvalidateRect(hwndList,0,FALSE); } +void RedrawHistoryList() +{ + ListView_SetItemState(hwndHistoryList, history.GetCursorPos(), LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED); + ListView_EnsureVisible(hwndHistoryList, history.GetCursorPos(), FALSE); + InvalidateRect(hwndHistoryList,0,FALSE); +} void RedrawRow(int index) { ListView_RedrawItems(hwndList,index,index); @@ -369,11 +425,13 @@ void RedrawRow(int index) void Tasedit_RewindFrame() { if (currFrameCounter > 0) JumpToFrame(currFrameCounter-1); + turbo = false; FollowPlayback(); } void Tasedit_ForwardFrame() { JumpToFrame(currFrameCounter+1); + turbo = false; FollowPlayback(); } @@ -399,7 +457,7 @@ void SeekingStart(int finish_frame) { seeking_start_frame = currFrameCounter; pauseframe = finish_frame; - turbo = (seeking_start_frame + FRAMES_TOO_FAR < finish_frame); + turbo = true; UnpauseEmulation(); } void SeekingStop() @@ -458,21 +516,19 @@ void RightClick(LPNMITEMACTIVATE info) RightClickMenu(info); } -void InputChanged() -{ - // keep input log - // update hot input - - - project.changed = true; -} void MarkersChanged() { project.changed = true; } +void InputChangedRec() +{ + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_RECORD, currFrameCounter,currFrameCounter)); +} void InvalidateGreenZone(int after) { + if (after < 0) return; + project.changed = true; if (currMovieData.greenZoneCount > after+1) { currMovieData.greenZoneCount = after+1; @@ -486,7 +542,6 @@ void InvalidateGreenZone(int after) JumpToFrame(pauseframe-1); else JumpToFrame(currFrameCounter); - turbo = true; } else { JumpToFrame(currMovieData.greenZoneCount-1); @@ -555,15 +610,17 @@ void ToggleJoypadBit(int column_index, int row_index, UINT KeyFlags) { currMovieData.records[*it].toggleBit(joy,bit); } - row_index = *selectionFrames.begin(); - } - else + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CHANGE, *selectionFrames.begin(), *selectionFrames.rbegin())); + } else { //update one row currMovieData.records[row_index].toggleBit(joy,bit); + if (currMovieData.records[row_index].checkBit(joy,bit)) + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, row_index, row_index)); + else + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, row_index, row_index)); } - InputChanged(); - InvalidateGreenZone(row_index); + } void SingleClick(LPNMITEMACTIVATE info) @@ -621,8 +678,8 @@ void CloneFrames() { int frames = selectionFrames.size(); - currMovieData.records.reserve(currMovieData.records.size()+frames); - currMovieData.frames_flags.reserve(currMovieData.frames_flags.size()+frames); + currMovieData.records.reserve(currMovieData.getNumRecords() + frames); + currMovieData.frames_flags.reserve(currMovieData.getNumRecords() + frames); //insert frames before each selection, but consecutive selection lines are accounted as single region frames = 1; @@ -638,10 +695,8 @@ void CloneFrames() frames = 1; } else frames++; } - UpdateList(); - InputChanged(); - InvalidateGreenZone(*selectionFrames.begin()); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLONE, *selectionFrames.begin())); } void InsertFrames() @@ -651,8 +706,8 @@ void InsertFrames() //this is going to be slow. //to keep this from being even slower than it would otherwise be, go ahead and reserve records - currMovieData.records.reserve(currMovieData.records.size()+frames); - currMovieData.frames_flags.reserve(currMovieData.frames_flags.size()+frames); + currMovieData.records.reserve(currMovieData.getNumRecords() + frames); + currMovieData.frames_flags.reserve(currMovieData.getNumRecords() + frames); //insert frames before each selection, but consecutive selection lines are accounted as single region frames = 1; @@ -668,14 +723,14 @@ void InsertFrames() frames = 1; } else frames++; } - UpdateList(); - InputChanged(); - InvalidateGreenZone(*selectionFrames.begin()); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, *selectionFrames.begin())); } void DeleteFrames() { + int start_index = *selectionFrames.begin(); + int end_index = *selectionFrames.rbegin(); //delete frames on each selection, going backwards for(TSelectionFrames::reverse_iterator it(selectionFrames.rbegin()); it != selectionFrames.rend(); it++) { @@ -684,41 +739,29 @@ void DeleteFrames() currMovieData.frames_flags.erase(currMovieData.frames_flags.begin() + *it); } // check if user deleted all frames - if (!currMovieData.records.size()) + if (!currMovieData.getNumRecords()) StartFromZero(); // reduce list - InputChanged(); UpdateList(); - int index = *selectionFrames.begin(); - int delete_index; - // reduce selection if needed - for(TSelectionFrames::reverse_iterator it(selectionFrames.rbegin()); it != selectionFrames.rend(); it++) - { - if ((int)*it < (int)currMovieData.records.size()) break; - delete_index = *it; - // reduce selection manually, because reduced list won't call ItemChanged for these rows - selectionFrames.erase(delete_index); - if (!selectionFrames.size()) break; - } - // reduce greenzone - InvalidateGreenZone(index); + + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_DELETE, start_index, end_index)); } -static void ClearFrames() +void ClearFrames(bool cut) { //clear input on each selection for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++) { currMovieData.records[*it].clear(); } - InputChanged(); - // reduce greenzone - int index = *selectionFrames.begin(); - InvalidateGreenZone(index); + if (cut) + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CUT, *selectionFrames.begin(), *selectionFrames.rbegin())); + else + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLEAR, *selectionFrames.begin(), *selectionFrames.rbegin())); } //the column set operation, for setting a button/Marker for a span of selected values -static void ColumnSet(int column) +void ColumnSet(int column) { if (column == COLUMN_FRAMENUM || column == COLUMN_FRAMENUM2) { @@ -766,8 +809,10 @@ static void ColumnSet(int column) // apply newValue for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++) currMovieData.records[*it].setBitValue(joy,button,newValue); - InputChanged(); - InvalidateGreenZone(*selectionFrames.begin()); + if (newValue) + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, *selectionFrames.begin(), *selectionFrames.rbegin())); + else + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, *selectionFrames.begin(), *selectionFrames.rbegin())); } } @@ -789,7 +834,7 @@ void SelectMidMarkers() { int center, upper_border, lower_border; int upper_marker, lower_marker; - int movie_size = currMovieData.records.size(); + int movie_size = currMovieData.getNumRecords(); // if selection size=0 then playback cursor is selected and serves as center if (selectionFrames.size()) @@ -825,7 +870,7 @@ void SelectMidMarkers() { // already selected all between markers - now select both markers or at least select the marker that is not outside movie range if (upper_marker < 0) upper_marker = 0; - if (lower_marker >= movie_size) lower_marker >= movie_size - 1; + if (lower_marker >= movie_size) lower_marker = movie_size - 1; for (int i = upper_marker; i <= lower_marker; ++i) { ListView_SetItemState(hwndList,i,LVIS_SELECTED,LVIS_SELECTED); @@ -833,7 +878,6 @@ void SelectMidMarkers() } else if (upper_border <= upper_marker && lower_border >= lower_marker) { // selected all between markers and both markers selected too - now deselect lower marker - ListView_SetItemState(hwndList,lower_marker,0,LVIS_SELECTED); for (int i = upper_marker; i < lower_marker; ++i) { ListView_SetItemState(hwndList,i,LVIS_SELECTED,LVIS_SELECTED); @@ -841,8 +885,7 @@ void SelectMidMarkers() } else if (upper_border == upper_marker && lower_border == lower_marker-1) { // selected all between markers and upper marker selected too - now deselect upper marker and (if lower marker < movie_size) reselect lower marker - ListView_SetItemState(hwndList,upper_marker,0,LVIS_SELECTED); - if (lower_marker >= movie_size) lower_marker >= movie_size - 1; + if (lower_marker >= movie_size) lower_marker = movie_size - 1; for (int i = upper_marker+1; i <= lower_marker; ++i) { ListView_SetItemState(hwndList,i,LVIS_SELECTED,LVIS_SELECTED); @@ -850,7 +893,6 @@ void SelectMidMarkers() } else if (upper_border == upper_marker+1 && lower_border == lower_marker) { // selected all between markers and lower marker selected too - now deselect lower marker (return to "selected all between markers") - ListView_SetItemState(hwndList,lower_marker,0,LVIS_SELECTED); for (int i = upper_marker + 1; i < lower_marker; ++i) { ListView_SetItemState(hwndList,i,LVIS_SELECTED,LVIS_SELECTED); @@ -858,8 +900,7 @@ void SelectMidMarkers() } } -//copies the current selection to the clipboard -static bool Copy() +bool Copy() { if (selectionFrames.size()==0) return false; @@ -925,18 +966,14 @@ static bool Copy() return true; } - -//cuts the current selection, copying it to the clipboard -static void Cut() +void Cut() { if (Copy()) { - ClearFrames(); + ClearFrames(true); } } - -//pastes the current clipboard selection into current inputlog -static bool Paste() +bool Paste() { bool result = false; if (selectionFrames.size()==0) @@ -959,9 +996,9 @@ static bool Paste() // Extract number of frames sscanf (pGlobal+3, "%d", &range); - if (currMovieData.records.size()(pos+range)) + if (currMovieData.getNumRecords() < pos+range) { - currMovieData.insertEmpty(currMovieData.records.size(),pos+range-currMovieData.records.size()); + currMovieData.insertEmpty(currMovieData.getNumRecords(),pos+range-currMovieData.getNumRecords()); } pGlobal = strchr(pGlobal, '\n'); @@ -1011,8 +1048,7 @@ static bool Paste() pGlobal = strchr(pGlobal, '\n'); } - InputChanged(); - InvalidateGreenZone(*selectionFrames.begin()); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_PASTE, *selectionFrames.begin())); result = true; } @@ -1025,7 +1061,7 @@ static bool Paste() // --------------------------------------------------------------------------------- //The subclass wndproc for the listview header -static LRESULT APIENTRY HeaderWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) +LRESULT APIENTRY HeaderWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { @@ -1051,12 +1087,11 @@ static LRESULT APIENTRY HeaderWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lP } //The subclass wndproc for the listview -static LRESULT APIENTRY ListWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) +LRESULT APIENTRY ListWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_CHAR: - return 0; case WM_KILLFOCUS: return 0; case WM_NOTIFY: @@ -1075,59 +1110,17 @@ static LRESULT APIENTRY ListWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lPar return CallWindowProc(hwndList_oldWndProc,hWnd,msg,wParam,lParam); } -//All dialog initialization -static void InitDialog() +LRESULT APIENTRY HistoryListWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { - //prepare the listview - ListView_SetExtendedListViewStyleEx(hwndList, - LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES , - LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES ); - - //subclass the header - hwndHeader = ListView_GetHeader(hwndList); - hwndHeader_oldWndproc = (WNDPROC)SetWindowLong(hwndHeader,GWL_WNDPROC,(LONG)HeaderWndProc); - - //subclass the whole listview, so we can block some keystrokes - hwndList_oldWndProc = (WNDPROC)SetWindowLong(hwndList,GWL_WNDPROC,(LONG)ListWndProc); - - //setup all images for the listview - HIMAGELIST himglist = ImageList_Create(8,12,ILC_COLOR8 | ILC_MASK,1,1); - HBITMAP bmp = LoadBitmap(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW)); - ImageList_AddMasked(himglist, bmp, 0xFF00FF); - DeleteObject(bmp); - ListView_SetImageList(hwndList,himglist,LVSIL_SMALL); - //doesnt work well?? - //HIMAGELIST himglist = ImageList_LoadImage(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW),12,1,RGB(255,0,255),IMAGE_BITMAP,LR_DEFAULTCOLOR); - - //setup columns - LVCOLUMN lvc; - int colidx=0; - // icons column - lvc.mask = LVCF_WIDTH; - lvc.cx = 12; - ListView_InsertColumn(hwndList, colidx++, &lvc); - // frame number column - lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT; - lvc.fmt = LVCFMT_CENTER; - lvc.cx = 75; - lvc.pszText = "Frame#"; - ListView_InsertColumn(hwndList, colidx++, &lvc); - // pads columns - lvc.cx = 21; - // add pads 1 and 2 - for (int joy = 0; joy < 2; ++joy) + switch(msg) { - for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn) - { - lvc.pszText = buttonNames[btn]; - ListView_InsertColumn(hwndList, colidx++, &lvc); - } + case WM_CHAR: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_KILLFOCUS: + return 0; } - // add pads 3 and 4 and frame_number2 - if (currMovieData.fourscore) AddFourscore(); - - //the initial update - UpdateTasEdit(); + return CallWindowProc(hwndList_oldWndProc,hWnd,msg,wParam,lParam); } void AddFourscore() @@ -1155,8 +1148,6 @@ void AddFourscore() EnableWindow(hwndRB_Rec4P, true); // change eoptions FCEUI_SetInputFourscore(true); - - } void RemoveFourscore() { @@ -1170,8 +1161,6 @@ void RemoveFourscore() EnableWindow(hwndRB_Rec4P, false); // change eoptions FCEUI_SetInputFourscore(false); - - } void UncheckRecordingRadioButtons() @@ -1213,18 +1202,6 @@ void RecheckRecordingRadioButtons() } } -//Creates a new TASEdit Project -static void NewProject() -{ - //determine if current project changed - //if so, ask to save changes - //close current project - if (!AskSaveProject()) return; - - //TODO: close current project instance, create a new one with a non-parameterized constructor -} - -//Opens a new Project file void OpenProject() { if (!AskSaveProject()) return; @@ -1274,13 +1251,16 @@ void OpenProject() bool last_fourscore = currMovieData.fourscore; // Load project project.LoadProject(project.GetProjectFile()); + UpdateList(); + UpdateHistoryList(); + RedrawHistoryList(); + // update fourscore status if (last_fourscore && !currMovieData.fourscore) RemoveFourscore(); else if (!last_fourscore && currMovieData.fourscore) AddFourscore(); SeekingStop(); FollowPlayback(); - //UpdateTasEdit(); RedrawTasedit(); RedrawWindowCaption(); } @@ -1391,23 +1371,24 @@ static void Export() } } -static void Truncate() +void Truncate() { int frame = currFrameCounter; if (selectionFrames.size()) { frame=*selectionFrames.begin(); - JumpToFrame(frame); ClearSelection(); } - currMovieData.truncateAt(frame+1); - InputChanged(); - UpdateList(); - InvalidateGreenZone(frame); + if (currMovieData.getNumRecords() > frame+1) + { + currMovieData.truncateAt(frame+1); + UpdateList(); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_TRUNCATE, frame+1)); + } } -//likewise, handles a changed item range from the listview -static void ItemRangeChanged(NMLVODSTATECHANGE* info) +//used to track selection +void ItemRangeChanged(NMLVODSTATECHANGE* info) { bool ON = !(info->uOldState & LVIS_SELECTED) && (info->uNewState & LVIS_SELECTED); bool OFF = (info->uOldState & LVIS_SELECTED) && !(info->uNewState & LVIS_SELECTED); @@ -1419,10 +1400,7 @@ static void ItemRangeChanged(NMLVODSTATECHANGE* info) for(int i=info->iFrom;i<=info->iTo;i++) selectionFrames.erase(i); } - -//handles a changed item from the listview -//used to track selection -static void ItemChanged(NMLISTVIEW* info) +void ItemChanged(NMLISTVIEW* info) { int item = info->iItem; @@ -1439,7 +1417,7 @@ static void ItemChanged(NMLISTVIEW* info) } else if (ON) { // select all - for(int i = 0; i < currMovieData.records.size(); i++) + for(int i = currMovieData.getNumRecords() - 1; i >= 0; i--) { selectionFrames.insert(i); } @@ -1469,8 +1447,8 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar // save references to dialog items hwndList = GetDlgItem(hwndDlg, IDC_LIST1); listItems = ListView_GetCountPerPage(hwndList); - FCEU_printf("listItems = %d\n\n",listItems); - + hwndHistoryList = GetDlgItem(hwndDlg, IDC_HISTORYLIST); + hwndBookmarksList = GetDlgItem(hwndDlg, IDC_BOOKMARKSLIST); hwndProgressbar = GetDlgItem(hwndDlg, IDC_PROGRESS1); SendMessage(hwndProgressbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSBAR_WIDTH)); hwndRewind = GetDlgItem(hwndDlg, TASEDIT_REWIND); @@ -1481,7 +1459,6 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar hwndRB_Rec2P = GetDlgItem(hwndDlg, IDC_RADIO4); hwndRB_Rec3P = GetDlgItem(hwndDlg, IDC_RADIO5); hwndRB_Rec4P = GetDlgItem(hwndDlg, IDC_RADIO6); - InitDialog(); break; case WM_MOVE: @@ -1535,6 +1512,22 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar ListView_RedrawItems(hwndList,end,end); break; */ + } + break; + case IDC_HISTORYLIST: + switch(((LPNMHDR)lParam)->code) + { + case NM_CUSTOMDRAW: + SetWindowLong(hwndDlg, DWL_MSGRESULT, HistoryCustomDraw((NMLVCUSTOMDRAW*)lParam)); + return TRUE; + case LVN_GETDISPINFO: + HistoryGetDispInfo((NMLVDISPINFO*)lParam); + break; + case NM_CLICK: + case NM_DBLCLK: + case NM_RCLICK: + HistoryClick((LPNMITEMACTIVATE)lParam); + break; } break; @@ -1565,9 +1558,6 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar case WM_COMMAND: switch(LOWORD(wParam)) { - case ID_FILE_NEWPROJECT: - NewProject(); - break; case ID_FILE_OPENPROJECT: OpenProject(); break; @@ -1633,14 +1623,12 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar // insert at selection int index = *selectionFrames.begin(); currMovieData.insertEmpty(index,frames); - InputChanged(); - InvalidateGreenZone(index); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, index)); } else { // insert at playback cursor currMovieData.insertEmpty(currFrameCounter,frames); - InputChanged(); - InvalidateGreenZone(currFrameCounter); + InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, currFrameCounter)); } } } @@ -1697,6 +1685,10 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar CheckMenuItem(hmenu, ID_VIEW_SHOWDOTINEMPTYCELLS, TASEdit_show_dot?MF_CHECKED : MF_UNCHECKED); RedrawList(); break; + case ID_VIEW_JUMPWHENMAKINGUNDO: + TASEdit_jump_to_undo ^= 1; + CheckMenuItem(hmenu, ID_VIEW_JUMPWHENMAKINGUNDO, TASEdit_jump_to_undo?MF_CHECKED : MF_UNCHECKED); + break; case ACCEL_CTRL_P: case CHECK_AUTORESTORE_PLAYBACK: //switch "Auto-restore last playback position" flag @@ -1707,12 +1699,12 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar { //open input dialog int new_capacity = TASEdit_greenzone_capacity; - if(CWin32InputBox::GetInteger("Greenzone capacity", "Keep savestates for how many frames?", new_capacity, hwndDlg) == IDOK) + if(CWin32InputBox::GetInteger("Greenzone capacity", "Keep savestates for how many frames?", new_capacity, hwndDlg) == IDOK) { - if (new_capacity < GREENZONE_MIN_CAPACITY) - new_capacity = GREENZONE_MIN_CAPACITY; - else if (new_capacity > GREENZONE_MAX_CAPACITY) - new_capacity = GREENZONE_MAX_CAPACITY; + if (new_capacity < GREENZONE_CAPACITY_MIN) + new_capacity = GREENZONE_CAPACITY_MIN; + else if (new_capacity > GREENZONE_CAPACITY_MAX) + new_capacity = GREENZONE_CAPACITY_MAX; if (new_capacity < TASEdit_greenzone_capacity) { TASEdit_greenzone_capacity = new_capacity; @@ -1722,6 +1714,27 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar } break; } + case ID_CONFIG_SETMAXUNDOLEVELS: + { + //open input dialog + int new_size = TasEdit_undo_levels; + if(CWin32InputBox::GetInteger("Max undo levels", "Keep history of how many changes?", new_size, hwndDlg) == IDOK) + { + if (new_size < UNDO_LEVELS_MIN) + new_size = UNDO_LEVELS_MIN; + else if (new_size > UNDO_LEVELS_MAX) + new_size = UNDO_LEVELS_MAX; + if (new_size != TasEdit_undo_levels) + { + TasEdit_undo_levels = new_size; + history.init(TasEdit_undo_levels); + // hot changes were cleared, so update list + RedrawList(); + //RedrawUndoList(); + } + } + break; + } case ID_CONFIG_MUTETURBO: muteTurbo ^= 1; CheckMenuItem(hmenu, ID_CONFIG_MUTETURBO, muteTurbo?MF_CHECKED : MF_UNCHECKED); @@ -1773,6 +1786,30 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar case ID_SELECTED_CLONE: if (selectionFrames.size()) CloneFrames(); break; + case ACCEL_CTRL_Z: + case ID_EDIT_UNDO: + { + int result = history.undo(); + if (result >= 0) + { + FollowUndo(); + UpdateList(); + InvalidateGreenZone(result); + } + break; + } + case ACCEL_CTRL_Y: + case ID_EDIT_REDO: + { + int result = history.redo(); + if (result >= 0) + { + FollowRedo(); + UpdateList(); + InvalidateGreenZone(result); + } + break; + } } break; @@ -1791,11 +1828,52 @@ int FindBeginningOfGreenZone(int starting_index) } return starting_index; } + +bool CheckItemVisible(int frame) +{ + int top = ListView_GetTopIndex(hwndList); + // in fourscore there's horizontal scrollbar which takes one row for itself + if (frame >= top && frame < top + listItems - (currMovieData.fourscore)?1:0) + return true; + return false; +} + void FollowPlayback() { if (TASEdit_follow_playback) ListView_EnsureVisible(hwndList,currFrameCounter,FALSE); } +void FollowUndo() +{ + int jump_frame = history.GetNextToCurrentSnapshot().jump_frame; + if (TASEdit_jump_to_undo && jump_frame >= 0) + { + if (!CheckItemVisible(jump_frame)) + { + ListView_EnsureVisible(hwndList, currMovieData.getNumRecords()-1, true); + ListView_EnsureVisible(hwndList, jump_frame, false); + } + } + // init undo hint + undo_hint_pos = jump_frame; + undo_hint_time = clock() + UNDO_HINT_TIME; +} +void FollowRedo() +{ + int jump_frame = history.GetCurrentSnapshot().jump_frame; + if (TASEdit_jump_to_undo && jump_frame >= 0) + { + if (!CheckItemVisible(jump_frame)) + { + ListView_EnsureVisible(hwndList, currMovieData.getNumRecords()-1, true); + ListView_EnsureVisible(hwndList, jump_frame, false); + } + } + // init undo hint + undo_hint_pos = jump_frame; + undo_hint_time = clock() + UNDO_HINT_TIME; +} + void EnterTasEdit() { if(!FCEU_IsValidUI(FCEUI_TASEDIT)) return; @@ -1831,6 +1909,7 @@ void EnterTasEdit() CheckDlgButton(hwndTasEdit, CHECK_FOLLOW_CURSOR, TASEdit_follow_playback?MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu, ID_VIEW_SHOW_LAG_FRAMES, TASEdit_show_lag_frames?MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu, ID_VIEW_SHOW_MARKERS, TASEdit_show_markers?MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hmenu, ID_VIEW_JUMPWHENMAKINGUNDO, TASEdit_jump_to_undo?MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu, ID_CONFIG_BINDMARKERSTOINPUT, TASEdit_bind_markers?MF_CHECKED : MF_UNCHECKED); CheckDlgButton(hwndTasEdit,CHECK_AUTORESTORE_PLAYBACK,TASEdit_restore_position?BST_CHECKED:BST_UNCHECKED); CheckMenuItem(hmenu, ID_CONFIG_MUTETURBO, muteTurbo?MF_CHECKED : MF_UNCHECKED); @@ -1838,7 +1917,7 @@ void EnterTasEdit() SetWindowPos(hwndTasEdit,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER); } - project.init(); + // either start new movie or use current movie if (movieMode == MOVIEMODE_INACTIVE) { @@ -1856,16 +1935,72 @@ void EnterTasEdit() multitrack_recording_joypad = MULTITRACK_RECORDING_ALL; RecheckRecordingRadioButtons(); movieMode = MOVIEMODE_TASEDIT; + + //prepare the main listview + ListView_SetExtendedListViewStyleEx(hwndList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES); + //subclass the header + hwndHeader = ListView_GetHeader(hwndList); + hwndHeader_oldWndproc = (WNDPROC)SetWindowLong(hwndHeader,GWL_WNDPROC,(LONG)HeaderWndProc); + //subclass the whole listview + hwndList_oldWndProc = (WNDPROC)SetWindowLong(hwndList,GWL_WNDPROC,(LONG)ListWndProc); + //setup images for the listview + HIMAGELIST himglist = ImageList_Create(8,12,ILC_COLOR8 | ILC_MASK,1,1); + HBITMAP bmp = LoadBitmap(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW)); + ImageList_AddMasked(himglist, bmp, 0xFF00FF); + DeleteObject(bmp); + ListView_SetImageList(hwndList,himglist,LVSIL_SMALL); + //setup columns + LVCOLUMN lvc; + int colidx=0; + // icons column + lvc.mask = LVCF_WIDTH; + lvc.cx = 13; + ListView_InsertColumn(hwndList, colidx++, &lvc); + // frame number column + lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT; + lvc.fmt = LVCFMT_CENTER; + lvc.cx = 75; + lvc.pszText = "Frame#"; + ListView_InsertColumn(hwndList, colidx++, &lvc); + // pads columns + lvc.cx = 21; + // add pads 1 and 2 + for (int joy = 0; joy < 2; ++joy) + { + for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn) + { + lvc.pszText = buttonNames[btn]; + ListView_InsertColumn(hwndList, colidx++, &lvc); + } + } + // add pads 3 and 4 and frame_number2 + if (currMovieData.fourscore) AddFourscore(); + UpdateList(); + + //prepare the history listview + ListView_SetExtendedListViewStyleEx(hwndHistoryList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES); + //subclass the whole listview + hwndHistoryList_oldWndProc = (WNDPROC)SetWindowLong(hwndHistoryList,GWL_WNDPROC,(LONG)HistoryListWndProc); + lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT; + lvc.cx = 200; + lvc.fmt = LVCFMT_LEFT; + ListView_InsertColumn(hwndHistoryList, 0, &lvc); + // init variables lastCursor = -1; old_project_changed = false; old_pauseframe = 0; old_show_pauseframe = show_pauseframe = false; + undo_hint_pos = old_undo_hint_pos = undo_hint_time = -1; + old_show_undo_hint = show_undo_hint = false; old_rewind_button_state = rewind_button_state = false; old_forward_button_state = forward_button_state = false; old_emu_paused = emu_paused = true; SeekingStop(); currMovieData.TryDumpIncremental(); + project.init(&history); + history.init(TasEdit_undo_levels); + SetFocus(hwndHistoryList); FCEU_DispMessage("Tasedit engaged",0); } bool ExitTasEdit() @@ -1890,8 +2025,68 @@ bool ExitTasEdit() JoystickClearBackgroundAccessBit(JOYBACKACCESS_TASEDIT); // release memory currMovieData.clearGreenzone(); + history.free(); movieMode = MOVIEMODE_INACTIVE; FCEU_DispMessage("Tasedit disengaged",0); CreateCleanMovie(); return true; } +// ------------------------------------------------------------------------------- +void HistoryGetDispInfo(NMLVDISPINFO* nmlvDispInfo) +{ + LVITEM& item = nmlvDispInfo->item; + if(item.mask & LVIF_TEXT) + strcpy(item.pszText, history.GetItemDesc(item.iItem)); +} + +LONG HistoryCustomDraw(NMLVCUSTOMDRAW* msg) +{ + switch(msg->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + { + if (history.GetItemCoherence(msg->nmcd.dwItemSpec)) + msg->clrTextBk = HISTORY_COHERENT_COLOR; + else + msg->clrTextBk = HISTORY_NORMAL_COLOR; + return CDRF_DODEFAULT; + } + default: + return CDRF_DODEFAULT; + } + +} + +void HistoryClick(LPNMITEMACTIVATE info) +{ + // jump to pointed input snapshot + int item = info->iItem; + if (item >= 0) + { + int previous_item = history.GetCursorPos(); + int result = history.jump(item); + if (result >= 0) + { + if (item < previous_item) FollowUndo(); else FollowRedo(); + UpdateList(); + InvalidateGreenZone(result); + return; + } + } + RedrawHistoryList(); +} + +void UpdateHistoryList() +{ + //update the number of items in the history list + int currLVItemCount = ListView_GetItemCount(hwndHistoryList); + int history_size = history.GetTotalItems(); + if(currLVItemCount != history_size) + { + ListView_SetItemCountEx(hwndHistoryList,history_size,LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL); + RedrawHistoryList(); + } +} + diff --git a/src/drivers/win/tasedit.h b/src/drivers/win/tasedit.h index 973078ab..18d6dc57 100644 --- a/src/drivers/win/tasedit.h +++ b/src/drivers/win/tasedit.h @@ -1,14 +1,21 @@ -#include "movie.h" -#define FRAMES_TOO_FAR 60 + #define NUM_JOYPADS 4 #define NUM_JOYPAD_BUTTONS 8 -#define GREENZONE_DEFAULT_CAPACITY 100000 -#define GREENZONE_MIN_CAPACITY 1 -#define GREENZONE_MAX_CAPACITY 200000 // maybe even more #define PAUSEFRAME_BLINKING_PERIOD 100 #define PROGRESSBAR_WIDTH 200 #define HOLD_REPEAT_DELAY 250 // in milliseconds + +#define GREENZONE_CAPACITY_DEFAULT 100000 +#define GREENZONE_CAPACITY_MIN 1 +#define GREENZONE_CAPACITY_MAX 200000 // maybe even more + +#define UNDO_LEVELS_MIN 1 +#define UNDO_LEVELS_MAX 999 +#define UNDO_LEVELS_DEFAULT 100 + +#define UNDO_HINT_TIME 200 + // multitrack #define MULTITRACK_RECORDING_ALL 0 #define MULTITRACK_RECORDING_1P 1 @@ -54,31 +61,42 @@ #define DIGITS_IN_FRAMENUM 7 // listview colors #define NORMAL_FRAMENUM_COLOR 0xFFFFFF +#define UNDOHINT_FRAMENUM_COLOR 0xF9DDE6 #define MARKED_FRAMENUM_COLOR 0xC0FCFF #define CUR_MARKED_FRAMENUM_COLOR 0xDEF7F4 #define CUR_FRAMENUM_COLOR 0xFCF1CE #define GREENZONE_FRAMENUM_COLOR 0xDDFFDD #define LAG_FRAMENUM_COLOR 0xDBDAFF #define NORMAL_INPUT_COLOR1 0xF0F0F0 +#define UNDOHINT_INPUT_COLOR1 0xF6CCDD #define CUR_INPUT_COLOR1 0xF7E9B2 #define GREENZONE_INPUT_COLOR1 0xC3FFC3 #define LAG_INPUT_COLOR1 0xCCC8EE #define NORMAL_INPUT_COLOR2 0xDEDEDE +#define UNDOHINT_INPUT_COLOR2 0xE5B7CC #define CUR_INPUT_COLOR2 0xE4D8A8 #define GREENZONE_INPUT_COLOR2 0xAEE2AE #define LAG_INPUT_COLOR2 0xB8B3E2 +#define HISTORY_COHERENT_COLOR 0xF9DDE6 +#define HISTORY_NORMAL_COLOR 0xFFFFFF + // ----------------------------- void EnterTasEdit(); +void InitDialog(); bool ExitTasEdit(); void UpdateTasEdit(); void UpdateList(); -void UpdateProgressbar(int frame); -void InputChanged(); +void UpdateHistoryList(); +void UpdateProgressbar(int a, int b); +void InputChangedRec(); void InvalidateGreenZone(int after); bool JumpToFrame(int index); int FindBeginningOfGreenZone(int starting_index); +bool CheckItemVisible(int frame); void FollowPlayback(); +void FollowUndo(); +void FollowRedo(); void ClearSelection(); void ClearRowSelection(int index); void AddFourscore(); @@ -86,6 +104,7 @@ void RemoveFourscore(); void RedrawWindowCaption(); void RedrawTasedit(); void RedrawList(); +void RedrawHistoryList(); void RedrawRow(int index); void SeekingStart(int finish_frame); void SeekingStop(); @@ -108,3 +127,13 @@ void SelectMidMarkers(); void CloneFrames(); void InsertFrames(); void DeleteFrames(); +void ClearFrames(bool cut = false); +void ColumnSet(int column); +bool Copy(); +void Cut(); +bool Paste(); +void Truncate(); +void HistoryGetDispInfo(NMLVDISPINFO* nmlvDispInfo); +LONG HistoryCustomDraw(NMLVCUSTOMDRAW* msg); +void HistoryClick(LPNMITEMACTIVATE info); + diff --git a/src/drivers/win/taseditlib/inputhistory.cpp b/src/drivers/win/taseditlib/inputhistory.cpp new file mode 100644 index 00000000..8f7f7f73 --- /dev/null +++ b/src/drivers/win/taseditlib/inputhistory.cpp @@ -0,0 +1,600 @@ +//Implementation file of Input History and Input Snapshot classes (for Undo feature) + +#include "movie.h" +#include "inputhistory.h" +#include "zlib.h" + +extern void FCEU_printf(char *format, ...); +extern void RedrawHistoryList(); +extern void UpdateHistoryList(); +extern void UpdateProgressbar(int a, int b); + +char modCaptions[23][12] = {"Init", + "Change", + "Set", + "Unset", + "Insert", + "Delete", + "Truncate", + "Clear", + "Cut", + "Paste", + "PasteInsert", + "Clone", + "Record", + "Branch0", + "Branch1", + "Branch2", + "Branch3", + "Branch4", + "Branch5", + "Branch6", + "Branch7", + "Branch8", + "Branch9"}; +char joypadCaptions[4][5] = {"(1P)", "(2P)", "(3P)", "(4P)"}; + +INPUT_SNAPSHOT::INPUT_SNAPSHOT() +{ + +} + +void INPUT_SNAPSHOT::init(MovieData& md) +{ + // retrieve input data from movie data + size = md.getNumRecords(); + fourscore = md.fourscore; + int num = (fourscore)?4:2; + joysticks.resize(num*size); // it's much faster to have this format [joy + frame << JOY_POWER] than have [frame][joy] + hot_changes.resize(num*size * HOTCHANGE_BYTES_PER_JOY); + int pos = 0; + if (fourscore) + { + for (int frame = 0; frame < size; ++frame) + { + joysticks[pos++] = md.records[frame].joysticks[0]; + joysticks[pos++] = md.records[frame].joysticks[1]; + joysticks[pos++] = md.records[frame].joysticks[2]; + joysticks[pos++] = md.records[frame].joysticks[3]; + } + } else + { + for (int frame = 0; frame < size; ++frame) + { + joysticks[pos++] = md.records[frame].joysticks[0]; + joysticks[pos++] = md.records[frame].joysticks[1]; + } + } + coherent = true; + // save time to description + time_t raw_time; + time(&raw_time); + struct tm * timeinfo = localtime(&raw_time); + strftime(description, 10, "%H:%M:%S ", timeinfo); +} + +void INPUT_SNAPSHOT::toMovie(MovieData& md, int start) +{ + // write input data to movie data + md.records.resize(size); + md.frames_flags.resize(size); + if (fourscore) + { + int pos = start << 2; + for (int frame = start; frame < size; ++frame) + { + md.records[frame].joysticks[0] = joysticks[pos++]; + md.records[frame].joysticks[1] = joysticks[pos++]; + md.records[frame].joysticks[2] = joysticks[pos++]; + md.records[frame].joysticks[3] = joysticks[pos++]; + } + } else + { + int pos = start << 1; + for (int frame = start; frame < size; ++frame) + { + md.records[frame].joysticks[0] = joysticks[pos++]; + md.records[frame].joysticks[1] = joysticks[pos++]; + } + } +} + +void INPUT_SNAPSHOT::save(EMUFILE *os) +{ + // write vars + write32le(size, os); + if (fourscore) write8le(4, os); else write8le((uint8)0, os); + if (coherent) write8le(1, os); else write8le((uint8)0, os); + write32le(jump_frame, os); + // write description + int len = strlen(description); + write8le(len, os); + os->fwrite(&description[0], len); + // compress and save joysticks data + len = joysticks.size(); + int comprlen = (len>>9)+12 + len; + std::vector cbuf(comprlen); + int e = compress(cbuf.data(), (uLongf*)&comprlen,(uint8*)joysticks.data(),len); + // write size + write32le(comprlen, os); + os->fwrite(cbuf.data(), comprlen); + // compress and save hot_changes data + len = hot_changes.size(); + comprlen = (len>>9)+12 + len; + std::vector cbuf2(comprlen); + e = compress(cbuf2.data(),(uLongf*)&comprlen,(uint8*)hot_changes.data(),len); + // write size + write32le(comprlen, os); + os->fwrite(cbuf2.data(), comprlen); +} +bool INPUT_SNAPSHOT::load(EMUFILE *is) +{ + int len; + uint8 tmp; + // read vars + if (!read32le(&size, is)) return false; + if (!read8le(&tmp, is)) return false; + fourscore = (tmp != 0); + if (!read8le(&tmp, is)) return false; + coherent = (tmp != 0); + if (!read32le(&jump_frame, is)) return false; + // read description + if (!read8le(&tmp, is)) return false; + if (tmp < 0 || tmp >= SNAPSHOT_DESC_MAX_LENGTH) return false; + if (is->fread(&description[0], tmp) != tmp) return false; + description[tmp] = 0; // add '0' because it wasn't saved + // read and uncompress joysticks data + len = (fourscore)?4*size:2*size; + joysticks.resize(len); + // read size + int comprlen; + if (!read32le(&comprlen, is)) return false; + if (comprlen <= 0 || comprlen > len) return false; + std::vector cbuf(comprlen); + if (is->fread(cbuf.data(),comprlen) != comprlen) return false; + int e = uncompress((uint8*)joysticks.data(),(uLongf*)&len,cbuf.data(),comprlen); + if (e != Z_OK && e != Z_BUF_ERROR) return false; + // read and uncompress hot_changes data + len = (fourscore) ? 4*size*HOTCHANGE_BYTES_PER_JOY : 2*size*HOTCHANGE_BYTES_PER_JOY; + hot_changes.resize(len); + // read size + if (!read32le(&comprlen, is)) return false; + if (comprlen <= 0 || comprlen > len) return false; + std::vector cbuf2(comprlen); + if (is->fread(cbuf2.data(),comprlen) != comprlen) return false; + e = uncompress(hot_changes.data(),(uLongf*)&len,cbuf.data(),comprlen); + if (e != Z_OK && e != Z_BUF_ERROR) return false; + return true; +} +bool INPUT_SNAPSHOT::skipLoad(EMUFILE *is) +{ + int tmp; + uint8 tmp1; + // read vars + if (!read32le(&tmp, is)) return false; + if (!read8le(&tmp1, is)) return false; + if (!read8le(&tmp1, is)) return false; + if (!read32le(&tmp, is)) return false; + // read description + if (!read8le(&tmp1, is)) return false; + if (is->fseek(tmp1, SEEK_CUR) != 0) return false; + // read joysticks data + if (!read32le(&tmp, is)) return false; + if (is->fseek(tmp, SEEK_CUR) != 0) return false; + // read hot_changes data + if (!read32le(&tmp, is)) return false; + if (is->fseek(tmp, SEEK_CUR) != 0) return false; + return true; +} + +// return true if any difference is found +bool INPUT_SNAPSHOT::checkDiff(INPUT_SNAPSHOT& inp) +{ + if (size != inp.size) return true; + if (findFirstChange(inp) >= 0) + return true; + else + return false; +} + +// return true if joypads differ +bool INPUT_SNAPSHOT::checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy) +{ + if (fourscore) + { + int pos = frame << 2; + if (pos < (inp.size << 2)) + { + if (joysticks[pos+joy] != inp.joysticks[pos+joy]) return true; + } else + { + if (joysticks[pos+joy]) return true; + } + } else + { + int pos = frame << 1; + if (pos < (inp.size << 1)) + { + if (joysticks[pos+joy] != inp.joysticks[pos+joy]) return true; + } else + { + if (joysticks[pos+joy]) return true; + } + } + return false; +} +// return number of first frame of difference +int INPUT_SNAPSHOT::findFirstChange(INPUT_SNAPSHOT& inp, int start, int end) +{ + // search for differences to the specified end (or to size) + if (end < 0 || end >= size) end = size-1; + + if (fourscore) + { + int inp_end = inp.size << 2; + end = (end << 2) + 3; + for (int pos = start << 2; pos <= end; ++pos) + { + // if found different byte, or found emptiness in inp when there's non-zero value here + if (pos < inp_end) + { + if (joysticks[pos] != inp.joysticks[pos]) return (pos >> 2); + } else + { + if (joysticks[pos]) return (pos >> 2); + } + } + } else + { + int inp_end = inp.size << 1; + end = (end << 1) + 1; + for (int pos = start << 1; pos <= end; ++pos) + { + // if found different byte, or found emptiness in inp when there's non-zero value here + if (pos < inp_end) + { + if (joysticks[pos] != inp.joysticks[pos]) return (pos >> 1); + } else + { + if (joysticks[pos]) return (pos >> 1); + } + } + } + // if current size is less then previous return size-1 as the frame of difference + if (size < inp.size) return size-1; + + return -1; // no changes were found +} +int INPUT_SNAPSHOT::findFirstChange(MovieData& md) +{ + // search for differences from the beginning to the end of movie (or to size) + int end = md.getNumRecords()-1; + if (end >= size) end = size-1; + + if (fourscore) + { + for (int frame = 0, pos = 0; frame <= end; ++frame) + { + if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame; + if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame; + if (joysticks[pos++] != md.records[frame].joysticks[2]) return frame; + if (joysticks[pos++] != md.records[frame].joysticks[3]) return frame; + } + } else + { + for (int frame = 0, pos = 0; frame <= end; ++frame) + { + if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame; + if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame; + } + } + // if sizes differ, return last frame from the lesser of them + if (size != md.getNumRecords()) return end; + + return -1; // no changes were found +} + +void INPUT_SNAPSHOT::SetMaxHotChange(int frame, int absolute_button) +{ + if (frame < 0 || frame >= size) return; + // set max value (15) to the button hotness + if (fourscore) + { + // 32 buttons, 16bytes + if (absolute_button & 1) + // odd buttons (B, T, D, R) - set upper 4 bits of the byte + hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0xF0; + else + // even buttons (A, S, U, L) - set lower 4 bits of the byte + hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0x0F; + } else + { + // 16 buttons, 8bytes + if (absolute_button & 1) + // odd buttons (B, T, D, R) - set upper 4 bits of the byte + hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0xF0; + else + // even buttons (A, S, U, L) - set lower 4 bits of the byte + hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0x0F; + } +} +int INPUT_SNAPSHOT::GetHotChangeInfo(int frame, int absolute_button) +{ + if (frame < 0 || frame >= size) return 0; + if (absolute_button < 0 || absolute_button > 31) return 0; + + uint8 val; + if (fourscore) + // 32 buttons, 16bytes + val = hot_changes[(frame << 4) + (absolute_button >> 1)]; + else + // 16 buttons, 8bytes + val = hot_changes[(frame << 3) + (absolute_button >> 1)]; + + if (absolute_button & 1) + // odd buttons (B, T, D, R) - upper 4 bits of the byte + return val >> 4; + else + // even buttons (A, S, U, L) - lower 4 bits of the byte + return val & 15; +} +// ----------------------------------------------------------------------------- +INPUT_HISTORY::INPUT_HISTORY() +{ + +} + +void INPUT_HISTORY::init(int new_size) +{ + history_size = new_size + 1; + // clear snapshots history + history_total_items = 0; + input_snapshots.resize(history_size); + history_start_pos = 0; + history_cursor_pos = -1; + // create initial snapshot + INPUT_SNAPSHOT inp; + inp.init(currMovieData); + strcat(inp.description, modCaptions[0]); + inp.jump_frame = -1; + AddInputSnapshotToHistory(inp); +} +void INPUT_HISTORY::free() +{ + input_snapshots.resize(0); +} + +// returns frame of first input change (for greenzone invalidation) +int INPUT_HISTORY::jump(int new_pos) +{ + if (new_pos < 0) new_pos = 0; else if (new_pos >= history_total_items) new_pos = history_total_items-1; + // if nothing is done, do not invalidate greenzone + if (new_pos == history_cursor_pos) return -1; + + // make jump + history_cursor_pos = new_pos; + int real_pos = (history_start_pos + history_cursor_pos) % history_size; + + int first_change = input_snapshots[real_pos].findFirstChange(currMovieData); + if (first_change < 0) return -1; // if somehow there's no changes + + // update current movie + input_snapshots[real_pos].toMovie(currMovieData, first_change); + RedrawHistoryList(); + return first_change; +} +int INPUT_HISTORY::undo() +{ + return jump(history_cursor_pos - 1); +} +int INPUT_HISTORY::redo() +{ + return jump(history_cursor_pos + 1); +} +// ---------------------------- +INPUT_SNAPSHOT& INPUT_HISTORY::GetCurrentSnapshot() +{ + return input_snapshots[(history_start_pos + history_cursor_pos) % history_size]; +} +INPUT_SNAPSHOT& INPUT_HISTORY::GetNextToCurrentSnapshot() +{ + if (history_cursor_pos < history_total_items) + return input_snapshots[(history_start_pos + history_cursor_pos + 1) % history_size]; + else + return input_snapshots[(history_start_pos + history_cursor_pos) % history_size]; +} +int INPUT_HISTORY::GetCursorPos() +{ + return history_cursor_pos; +} +int INPUT_HISTORY::GetTotalItems() +{ + return history_total_items; +} +char* INPUT_HISTORY::GetItemDesc(int pos) +{ + return input_snapshots[(history_start_pos + pos) % history_size].description; +} +bool INPUT_HISTORY::GetItemCoherence(int pos) +{ + return input_snapshots[(history_start_pos + pos) % history_size].coherent; +} +// ---------------------------- +void INPUT_HISTORY::AddInputSnapshotToHistory(INPUT_SNAPSHOT &inp) +{ + // history uses conveyor of snapshots (vector with fixed size) to aviod resizing which is awfully expensive with such large objects as INPUT_SNAPSHOT + int real_pos; + if (history_cursor_pos+1 >= history_size) + { + // reached the end of available history_size - move history_start_pos (thus deleting older snapshot) + history_cursor_pos = history_size-1; + history_start_pos = (history_start_pos + 1) % history_size; + } else + { + // didn't reach the end of history yet + history_cursor_pos++; + if (history_cursor_pos < history_total_items) + { + // overwrite old snapshot + real_pos = (history_start_pos + history_cursor_pos) % history_size; + // compare with the snapshot we're going to overwrite, if it's different then break the chain of coherent snapshots + if (input_snapshots[real_pos].checkDiff(inp)) + { + for (int i = history_cursor_pos+1; i < history_total_items; ++i) + { + real_pos = (history_start_pos + i) % history_size; + input_snapshots[real_pos].coherent = false; + } + } + } else + { + // add new smapshot + history_total_items = history_cursor_pos+1; + UpdateHistoryList(); + } + } + // write snapshot + real_pos = (history_start_pos + history_cursor_pos) % history_size; + input_snapshots[real_pos] = inp; + RedrawHistoryList(); +} + +// returns frame of first actual change +int INPUT_HISTORY::RegisterInputChanges(int mod_type, int start, int end) +{ + // create new input shanshot + INPUT_SNAPSHOT inp; + inp.init(currMovieData); + // check if there are differences from latest snapshot + int real_pos = (history_start_pos + history_cursor_pos) % history_size; + int first_changes = inp.findFirstChange(input_snapshots[real_pos], start, end); + if (first_changes >= 0) + { + // differences found + // fade old hot_changes by 1 + + // highlight new hot changes + + // fill description + strcat(inp.description, modCaptions[mod_type]); + switch (mod_type) + { + case MODTYPE_CHANGE: + case MODTYPE_SET: + case MODTYPE_UNSET: + case MODTYPE_TRUNCATE: + case MODTYPE_CLEAR: + case MODTYPE_CUT: + case MODTYPE_BRANCH_0: case MODTYPE_BRANCH_1: + case MODTYPE_BRANCH_2: case MODTYPE_BRANCH_3: + case MODTYPE_BRANCH_4: case MODTYPE_BRANCH_5: + case MODTYPE_BRANCH_6: case MODTYPE_BRANCH_7: + case MODTYPE_BRANCH_8: case MODTYPE_BRANCH_9: + { + inp.jump_frame = first_changes; + break; + } + case MODTYPE_INSERT: + case MODTYPE_DELETE: + case MODTYPE_PASTE: + case MODTYPE_PASTEINSERT: + case MODTYPE_CLONE: + { + // for these changes user prefers to see frame of attempted change (selection beginning), not frame of actual differences + inp.jump_frame = start; + break; + } + case MODTYPE_RECORD: + { + // add info which joypads were affected + int num = (inp.fourscore)?4:2; + for (int i = 0; i < num; ++i) + { + if (inp.checkJoypadDiff(input_snapshots[real_pos], first_changes, i)) + strcat(inp.description, joypadCaptions[i]); + } + inp.jump_frame = start; + } + } + // add upper and lower frame to description + char framenum[11]; + _itoa(start, framenum, 10); + strcat(inp.description, " "); + strcat(inp.description, framenum); + if (end > start) + { + _itoa(end, framenum, 10); + strcat(inp.description, "-"); + strcat(inp.description, framenum); + } + AddInputSnapshotToHistory(inp); + } + return first_changes; +} +// ---------------------------- +void INPUT_HISTORY::save(EMUFILE *os) +{ + int real_pos, last_tick = 0; + // write vars + write32le(history_cursor_pos, os); + write32le(history_total_items, os); + // write snapshots starting from history_start_pos + for (int i = 0; i < history_total_items; ++i) + { + real_pos = (history_start_pos + i) % history_size; + input_snapshots[real_pos].save(os); + UpdateProgressbar(i, history_total_items); + } +} +void INPUT_HISTORY::load(EMUFILE *is) +{ + int i = -1; + INPUT_SNAPSHOT inp; + // delete old snapshots + input_snapshots.resize(history_size); + // read vars + if (!read32le((uint32 *)&history_cursor_pos, is)) goto error; + if (!read32le((uint32 *)&history_total_items, is)) goto error; + history_start_pos = 0; + // read snapshots + int total = history_total_items; + if (history_total_items > history_size) + { + // user can't afford that much undo levels, skip some snapshots + int num_snapshots_to_skip = history_total_items - history_size; + // first try to skip snapshots over history_cursor_pos (future snapshots), because "redo" is less important than "undo" + int num_redo_snapshots = history_total_items-1 - history_cursor_pos; + if (num_snapshots_to_skip >= num_redo_snapshots) + { + // skip all redo snapshots + history_total_items = history_cursor_pos+1; + num_snapshots_to_skip -= num_redo_snapshots; + // and still need to skip some undo snapshots + for (i = 0; i < num_snapshots_to_skip; ++i) + if (!inp.skipLoad(is)) goto error; + total -= num_snapshots_to_skip; + history_cursor_pos -= num_snapshots_to_skip; + } + history_total_items -= num_snapshots_to_skip; + } + // load snapshots + for (i = 0; i < history_total_items; ++i) + { + // skip snapshots if current history_size is less then history_total_items + if (!input_snapshots[i].load(is)) goto error; + UpdateProgressbar(i, history_total_items); + } + // skip redo snapshots if needed + for (; i < total; ++i) + if (!inp.skipLoad(is)) goto error; + + return; +error: + // couldn't load full history - reset it + FCEU_printf("Error loading history\n"); + init(history_size-1); +} + + + + diff --git a/src/drivers/win/taseditlib/inputhistory.h b/src/drivers/win/taseditlib/inputhistory.h new file mode 100644 index 00000000..fec60a91 --- /dev/null +++ b/src/drivers/win/taseditlib/inputhistory.h @@ -0,0 +1,102 @@ +//Specification file for Input History and Input Snapshot classes + +#include + +#define HOTCHANGE_BYTES_PER_JOY 4 +#define SNAPSHOT_DESC_MAX_LENGTH 50 + +#define MODTYPE_INIT 0 +#define MODTYPE_CHANGE 1 +#define MODTYPE_SET 2 +#define MODTYPE_UNSET 3 +#define MODTYPE_INSERT 4 +#define MODTYPE_DELETE 5 +#define MODTYPE_TRUNCATE 6 +#define MODTYPE_CLEAR 7 +#define MODTYPE_CUT 8 +#define MODTYPE_PASTE 9 +#define MODTYPE_PASTEINSERT 10 +#define MODTYPE_CLONE 11 +#define MODTYPE_RECORD 12 +#define MODTYPE_BRANCH_0 13 +#define MODTYPE_BRANCH_1 14 +#define MODTYPE_BRANCH_2 15 +#define MODTYPE_BRANCH_3 16 +#define MODTYPE_BRANCH_4 17 +#define MODTYPE_BRANCH_5 18 +#define MODTYPE_BRANCH_6 19 +#define MODTYPE_BRANCH_7 20 +#define MODTYPE_BRANCH_8 21 +#define MODTYPE_BRANCH_9 22 + +class INPUT_SNAPSHOT +{ +public: + INPUT_SNAPSHOT(); + void init(MovieData& md); + void toMovie(MovieData& md, int start = 0); + + void save(EMUFILE *os); + bool load(EMUFILE *is); + bool skipLoad(EMUFILE *is); + + bool checkDiff(INPUT_SNAPSHOT& inp); + bool checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy); + int findFirstChange(INPUT_SNAPSHOT& inp, int start = 0, int end = -1); + int findFirstChange(MovieData& md); + + void SetMaxHotChange(int frame, int absolute_button); + int GetHotChangeInfo(int frame, int absolute_button); + + int size; // in frames + bool fourscore; + std::vector joysticks; // Format: joy0-for-frame0, joy1-for-frame0, joy2-for-frame0, joy3-for-frame0, joy0-for-frame1, joy1-for-frame1, ... + std::vector hot_changes; // Format: buttons01joy0-for-frame0, buttons23joy0-for-frame0, buttons45joy0-for-frame0, buttons67joy0-for-frame0, buttons01joy1-for-frame0, ... + + bool coherent; // indicates whether this state was made by inputchange of previous state + int jump_frame; // for jumping when making undo + char description[SNAPSHOT_DESC_MAX_LENGTH]; + +private: + +}; + +class INPUT_HISTORY +{ +public: + INPUT_HISTORY(); + void init(int new_size); + void free(); + + void save(EMUFILE *os); + void load(EMUFILE *is); + + int undo(); + int redo(); + int jump(int new_pos); + + void AddInputSnapshotToHistory(INPUT_SNAPSHOT &inp); + + int RegisterInputChanges(int mod_type, int start = 0, int end =-1); + + int InputChanged(int start, int end); + int InputInserted(int start); + int InputDeleted(int start); + + INPUT_SNAPSHOT& GetCurrentSnapshot(); + INPUT_SNAPSHOT& GetNextToCurrentSnapshot(); + int GetCursorPos(); + int GetTotalItems(); + char* GetItemDesc(int pos); + bool GetItemCoherence(int pos); + +private: + std::vector input_snapshots; + + int history_cursor_pos; + int history_start_pos; + int history_total_items; + int history_size; + +}; + diff --git a/src/drivers/win/taseditlib/taseditproj.cpp b/src/drivers/win/taseditlib/taseditproj.cpp index 05448ed0..6e84883b 100644 --- a/src/drivers/win/taseditlib/taseditproj.cpp +++ b/src/drivers/win/taseditlib/taseditproj.cpp @@ -3,21 +3,23 @@ //Contains all the TASEDit project and all files/settings associated with it //Also contains all methods for manipulating the project files/settings, and saving them to disk - -#include #include #include #include "../main.h" #include "taseditproj.h" -#include "movie.h" TASEDIT_PROJECT::TASEDIT_PROJECT() //Non parameterized constructor, loads project with default values { } -void TASEDIT_PROJECT::init() +void TASEDIT_PROJECT::init(INPUT_HISTORY* history_ptr) { + // keep references to other Taseditor objects + history = history_ptr; + //greenzone = greenzone_ptr; + //bookmarks_bookmarks_ptr; + projectName=""; fm2FileName=""; projectFile=""; @@ -66,6 +68,8 @@ bool TASEDIT_PROJECT::saveProject() ofs->fputc('\0'); // TODO: Add main branch name. currMovieData.dumpGreenzone(ofs); + history->save(ofs); + delete ofs; changed=false; @@ -86,12 +90,18 @@ bool TASEDIT_PROJECT::LoadProject(std::string PFN) char branchname; branchname = ifs.fgetc(); // TODO: Add main branch name. + + // try to load greenzone if (!currMovieData.loadGreenzone(&ifs)) { // there was some error while loading greenzone - reset playback to frame 0 poweron(true); currFrameCounter = 0; } - changed=false; + // try to load history + history->load(&ifs); + + changed = false; return true; } + diff --git a/src/drivers/win/taseditlib/taseditproj.h b/src/drivers/win/taseditlib/taseditproj.h index db4ce24c..039c00d6 100644 --- a/src/drivers/win/taseditlib/taseditproj.h +++ b/src/drivers/win/taseditlib/taseditproj.h @@ -16,8 +16,8 @@ //All notes are added to teh right columns //All ilog files are listed in the input log list -#include #include "movie.h" +#include "inputhistory.h" //The notes feature, displays user notes in the notes column struct TASENote @@ -48,7 +48,7 @@ class TASEDIT_PROJECT { public: TASEDIT_PROJECT(); - void init(); + void init(INPUT_HISTORY* history_ptr); std::string GetProjectName(); void SetProjectName(std::string e); @@ -59,14 +59,13 @@ public: std::string GetProjectFile(); void SetProjectFile(std::string e); - //Guess what these functions are for... bool saveProject(); bool LoadProject(std::string PFN); bool Export2FM2(std::string filename); //creates a fm2 out of header, comments, subtitles, and main branch input log, return false if any errors occur - void AddInputLog(std::vector records, std::string fn); //Receives a vector of movie records & a filename, and saves them to disk (as .log files), and adds filename to inputlog vector - bool changed; // If there are unsaved changes. + // public vars + bool changed; private: std::string projectName; //The TASEdit Project's name @@ -78,4 +77,6 @@ private: std::vector comments; std::vector subtitles; + // references to other important objects of Taseditor + INPUT_HISTORY* history; }; diff --git a/src/movie.cpp b/src/movie.cpp index 65027f70..1b87c09e 100644 --- a/src/movie.cpp +++ b/src/movie.cpp @@ -41,7 +41,7 @@ #include "./drivers/win/window.h" extern void AddRecentMovieFile(const char *filename); -extern void UpdateProgressbar(int frame); +extern void UpdateProgressbar(int a, int b); #endif @@ -598,7 +598,7 @@ int MovieData::dumpGreenzone(EMUFILE *os) { int start = os->ftell(); int frame, size; - int last_tick = PROGRESSBAR_UPDATE_MIN; + int last_tick = 0; // write size write32le(greenZoneCount, os); write32le(currFrameCounter, os); @@ -609,7 +609,7 @@ int MovieData::dumpGreenzone(EMUFILE *os) // update TASEditor progressbar from time to time if (frame / PROGRESSBAR_UPDATE_RATE > last_tick) { - UpdateProgressbar(frame); + UpdateProgressbar(frame, greenZoneCount); last_tick = frame / PROGRESSBAR_UPDATE_RATE; } #endif @@ -622,7 +622,7 @@ int MovieData::dumpGreenzone(EMUFILE *os) // write savestate size = savestates[frame].size(); write32le(size, os); - os->fwrite(&savestates[frame][0], size); + os->fwrite(savestates[frame].data(), size); } // write -1 as eof for greenzone @@ -637,7 +637,7 @@ bool MovieData::loadGreenzone(EMUFILE *is) clearGreenzone(); frames_flags.resize(records.size()); int frame = 0, prev_frame = 0, size = 0; - int last_tick = PROGRESSBAR_UPDATE_MIN; + int last_tick = 0; // read size if (read32le((uint32 *)&size, is)) { @@ -660,7 +660,7 @@ bool MovieData::loadGreenzone(EMUFILE *is) // update TASEditor progressbar from time to time if (frame / PROGRESSBAR_UPDATE_RATE > last_tick) { - UpdateProgressbar(frame); + UpdateProgressbar(frame, greenZoneCount); last_tick = frame / PROGRESSBAR_UPDATE_RATE; } #endif @@ -674,7 +674,7 @@ bool MovieData::loadGreenzone(EMUFILE *is) { // load savestate savestates[frame].resize(size); - if ((int)is->fread((char*)&savestates[frame][0],size) < size) break; + if ((int)is->fread(savestates[frame].data(),size) < size) break; prev_frame = frame; // successfully read one greenzone frame info } else { @@ -691,6 +691,7 @@ bool MovieData::loadGreenzone(EMUFILE *is) } else { // there was some error while reading greenzone + FCEU_printf("Error loading greenzone\n"); } } } @@ -774,6 +775,7 @@ static void LoadFM2_binarychunk(MovieData& movieData, EMUFILE* fp, int size) numRecords=movieData.loadFrameCount; movieData.records.resize(numRecords); + movieData.frames_flags.resize(numRecords); for(int i=0;iftell(); movieData.records[currcount].parse(&movieData, fp); int postparse = fp->ftell(); @@ -1208,18 +1211,14 @@ void FCEUMOV_AddInputState() } else { - // TODO: check if input actually changed - InputChanged(); // record buttons - if (currMovieData.greenZoneCount>currFrameCounter+1) - { - InvalidateGreenZone(currFrameCounter); - } // TODO: multitracking joyports[0].log(mr); joyports[1].log(mr); mr->commands = 0; + + InputChangedRec(); // TODO: don't call function explicitly, taseditor should catch changes in UpdateTasedit function } } else #endif diff --git a/src/movie.h b/src/movie.h index 076750cc..2dd2c7d3 100644 --- a/src/movie.h +++ b/src/movie.h @@ -4,8 +4,7 @@ #define LAG_FLAG_BIT 1 #define MARKER_FLAG_BIT 2 -#define PROGRESSBAR_UPDATE_RATE 3000 // in frames of greenzone -#define PROGRESSBAR_UPDATE_MIN 6000 / PROGRESSBAR_UPDATE_RATE +#define PROGRESSBAR_UPDATE_RATE 2000 // in frames of greenzone #include #include diff --git a/vc/vc10_fceux.vcxproj b/vc/vc10_fceux.vcxproj index ba402f81..d26067d3 100644 --- a/vc/vc10_fceux.vcxproj +++ b/vc/vc10_fceux.vcxproj @@ -418,6 +418,7 @@ $(IntDir)%(Filename)1.xdc + @@ -729,6 +730,7 @@ + @@ -851,6 +853,7 @@ $(OutDir)auxlib.lua;%(Outputs) + diff --git a/vc/vc10_fceux.vcxproj.filters b/vc/vc10_fceux.vcxproj.filters index 9cc1bd19..791f7a7e 100644 --- a/vc/vc10_fceux.vcxproj.filters +++ b/vc/vc10_fceux.vcxproj.filters @@ -907,6 +907,7 @@ boards + @@ -1351,6 +1352,7 @@ drivers\common + @@ -1368,6 +1370,7 @@ drivers\win\res +