/* --------------------------------------------------------------------------------- Implementation file of Markers_manager class Copyright (c) 2011-2013 AnS (The MIT License) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------------ Markers_manager - Manager of Markers [Single instance] * stores one snapshot of Markers, representing current state of Markers in the project * saves and loads the data from a project file. On error: clears the data * regularly ensures that the size of current Markers array is not less than the number of frames in current Input * implements all operations with Markers: setting Marker to a frame, removing Marker, inserting/deleting frames between Markers, truncating Markers array, changing Notes, finding frame for any given Marker, access to the data of Snapshot of Markers state * implements full/partial copying of data between two Snapshots of Markers state, and searching for first difference between two Snapshots of Markers state * also here's the code of searching for "similar" Notes * also here's the code of editing Marker Notes * also here's the code of Find Note dialog * stores resources: save id, properties of searching for similar Notes ------------------------------------------------------------------------------------ */ #include "fceu.h" #include "Qt/TasEditor/taseditor_project.h" #include "Qt/TasEditor/TasEditorWindow.h" // resources static char markers_save_id[MARKERS_ID_LEN] = "MARKERS"; static char markers_skipsave_id[MARKERS_ID_LEN] = "MARKERX"; //static char keywordDelimiters[] = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; MARKERS_MANAGER::MARKERS_MANAGER() { memset(findNoteString, 0, MAX_NOTE_LEN); } void MARKERS_MANAGER::init() { reset(); } void MARKERS_MANAGER::free() { markers.markersArray.resize(0); markers.notes.resize(0); } void MARKERS_MANAGER::reset() { free(); markerNoteEditMode = MARKER_NOTE_EDIT_NONE; currentIterationOfFindSimilar = 0; markers.notes.resize(1); markers.notes[0] = "Power on"; update(); } void MARKERS_MANAGER::update() { // the size of current markers_array must be no less then the size of Input if ((int)markers.markersArray.size() < currMovieData.getNumRecords()) markers.markersArray.resize(currMovieData.getNumRecords()); } void MARKERS_MANAGER::save(EMUFILE *os, bool really_save) { if (really_save) { setTasProjectProgressBarText("Saving Markers..."); setTasProjectProgressBar( 0, 100 ); // write "MARKERS" string os->fwrite(markers_save_id, MARKERS_ID_LEN); markers.resetCompressedStatus(); // must recompress data, because most likely it has changed since last compression markers.save(os); setTasProjectProgressBar( 100, 100 ); } else { // write "MARKERX" string, meaning that Markers are not saved os->fwrite(markers_skipsave_id, MARKERS_ID_LEN); } } // returns true if couldn't load bool MARKERS_MANAGER::load(EMUFILE *is, unsigned int offset) { if (offset) { if (is->fseek(offset, SEEK_SET)) goto error; } else { reset(); return false; } // read "MARKERS" string char save_id[MARKERS_ID_LEN]; if ((int)is->fread(save_id, MARKERS_ID_LEN) < MARKERS_ID_LEN) goto error; if (!strcmp(markers_skipsave_id, save_id)) { // string says to skip loading Markers FCEU_printf("No Markers in the file\n"); reset(); return false; } if (strcmp(markers_save_id, save_id)) goto error; // string is not valid if (markers.load(is)) goto error; // all ok return false; error: FCEU_printf("Error loading Markers\n"); reset(); return true; } // ----------------------------------------------------------------------------------------- int MARKERS_MANAGER::getMarkersArraySize() { return markers.markersArray.size(); } bool MARKERS_MANAGER::setMarkersArraySize(int newSize) { // if we are truncating, clear Markers that are gonna be erased (so that obsolete notes will be erased too) bool markers_changed = false; for (int i = markers.markersArray.size() - 1; i >= newSize; i--) { if (markers.markersArray[i]) { removeMarkerFromFrame(i); markers_changed = true; } } markers.markersArray.resize(newSize); return markers_changed; } int MARKERS_MANAGER::getMarkerAtFrame(int frame) { if (frame >= 0 && frame < (int)markers.markersArray.size()) return markers.markersArray[frame]; else return 0; } // finds and returns # of Marker starting from start_frame and searching up int MARKERS_MANAGER::getMarkerAboveFrame(int startFrame) { if (startFrame >= (int)markers.markersArray.size()) startFrame = markers.markersArray.size() - 1; for (; startFrame >= 0; startFrame--) if (markers.markersArray[startFrame]) return markers.markersArray[startFrame]; return 0; } // special version of the function int MARKERS_MANAGER::getMarkerAboveFrame(MARKERS& targetMarkers, int startFrame) { if (startFrame >= (int)targetMarkers.markersArray.size()) startFrame = targetMarkers.markersArray.size() - 1; for (; startFrame >= 0; startFrame--) if (targetMarkers.markersArray[startFrame]) return targetMarkers.markersArray[startFrame]; return 0; } // finds frame where the Marker is set int MARKERS_MANAGER::getMarkerFrameNumber(int marker_id) { for (int i = markers.markersArray.size() - 1; i >= 0; i--) if (markers.markersArray[i] == marker_id) return i; // didn't find return -1; } // returns number of new Marker int MARKERS_MANAGER::setMarkerAtFrame(int frame) { if (frame < 0) return 0; else if (frame >= (int)markers.markersArray.size()) markers.markersArray.resize(frame + 1); else if (markers.markersArray[frame]) return markers.markersArray[frame]; int marker_num = getMarkerAboveFrame(frame) + 1; markers.markersArray[frame] = marker_num; if (taseditorConfig->emptyNewMarkerNotes) markers.notes.insert(markers.notes.begin() + marker_num, 1, ""); else // copy previous Marker note markers.notes.insert(markers.notes.begin() + marker_num, 1, markers.notes[marker_num - 1]); // increase following Markers' ids int size = markers.markersArray.size(); for (frame++; frame < size; ++frame) if (markers.markersArray[frame]) markers.markersArray[frame]++; return marker_num; } void MARKERS_MANAGER::removeMarkerFromFrame(int frame) { if (markers.markersArray[frame]) { // erase corresponding note markers.notes.erase(markers.notes.begin() + markers.markersArray[frame]); // clear Marker markers.markersArray[frame] = 0; // decrease following Markers' ids int size = markers.markersArray.size(); for (frame++; frame < size; ++frame) if (markers.markersArray[frame]) markers.markersArray[frame]--; } } void MARKERS_MANAGER::toggleMarkerAtFrame(int frame) { if (frame >= 0 && frame < (int)markers.markersArray.size()) { if (markers.markersArray[frame]) removeMarkerFromFrame(frame); else setMarkerAtFrame(frame); } } bool MARKERS_MANAGER::eraseMarker(int frame, int numFrames) { bool markers_changed = false; if (frame < (int)markers.markersArray.size()) { if (numFrames == 1) { // erase 1 frame // if there's a Marker, first clear it if (markers.markersArray[frame]) { removeMarkerFromFrame(frame); markers_changed = true; } // erase 1 frame markers.markersArray.erase(markers.markersArray.begin() + frame); } else { // erase many frames if (frame + numFrames > (int)markers.markersArray.size()) numFrames = (int)markers.markersArray.size() - frame; // if there are Markers at those frames, first clear them for (int i = frame; i < (frame + numFrames); ++i) { if (markers.markersArray[i]) { removeMarkerFromFrame(i); markers_changed = true; } } // erase frames markers.markersArray.erase(markers.markersArray.begin() + frame, markers.markersArray.begin() + (frame + numFrames)); } // check if there were some Markers after this frame // since these Markers were shifted, markers_changed should be set to true if (!markers_changed) { for (int i = markers.markersArray.size() - 1; i >= frame; i--) { if (markers.markersArray[i]) { markers_changed = true; // Markers moved break; } } } } return markers_changed; } bool MARKERS_MANAGER::insertEmpty(int at, int numFrames) { if (at == -1) { // append blank frames markers.markersArray.resize(markers.markersArray.size() + numFrames); return false; } else { bool markers_changed = false; // first check if there are Markers after the frame for (int i = markers.markersArray.size() - 1; i >= at; i--) { if (markers.markersArray[i]) { markers_changed = true; // Markers moved break; } } markers.markersArray.insert(markers.markersArray.begin() + at, numFrames, 0); return markers_changed; } } int MARKERS_MANAGER::getNotesSize() { return markers.notes.size(); } std::string MARKERS_MANAGER::getNoteCopy(int index) { if (index >= 0 && index < (int)markers.notes.size()) return markers.notes[index]; else return markers.notes[0]; } // special version of the function std::string MARKERS_MANAGER::getNoteCopy(MARKERS& targetMarkers, int index) { if (index >= 0 && index < (int)targetMarkers.notes.size()) return targetMarkers.notes[index]; else return targetMarkers.notes[0]; } void MARKERS_MANAGER::setNote(int index, const char* newText) { if (index >= 0 && index < (int)markers.notes.size()) markers.notes[index] = newText; } // --------------------------------------------------------------------------------------- void MARKERS_MANAGER::makeCopyOfCurrentMarkersTo(MARKERS& destination) { destination.markersArray = markers.markersArray; destination.notes = markers.notes; destination.resetCompressedStatus(); } void MARKERS_MANAGER::restoreMarkersFromCopy(MARKERS& source) { markers.markersArray = source.markersArray; markers.notes = source.notes; } // return true only when difference is found before end frame (not including end frame) bool MARKERS_MANAGER::checkMarkersDiff(MARKERS& theirMarkers) { int end_my = getMarkersArraySize() - 1; int end_their = theirMarkers.markersArray.size() - 1; int min_end = end_my; int i; // 1 - check if there are any Markers after min_end if (end_my < end_their) { for (i = end_their; i > min_end; i--) if (theirMarkers.markersArray[i]) return true; } else if (end_my > end_their) { min_end = end_their; for (i = end_my; i > min_end; i--) if (markers.markersArray[i]) return true; } // 2 - check if there's any difference before min_end for (i = min_end; i >= 0; i--) { if (markers.markersArray[i] != theirMarkers.markersArray[i]) return true; else if (markers.markersArray[i] && // not empty markers.notes[markers.markersArray[i]].compare(theirMarkers.notes[theirMarkers.markersArray[i]])) // notes differ return true; } // 3 - check if there's difference between 0th Notes if (markers.notes[0].compare(theirMarkers.notes[0])) return true; // no difference found return false; } // ------------------------------------------------------------------------------------ // custom ordering function, used by std::sort bool ordering(const std::pair& d1, const std::pair& d2) { return d1.second < d2.second; } void MARKERS_MANAGER::findSimilarNote() { currentIterationOfFindSimilar = 0; findNextSimilarNote(); } void MARKERS_MANAGER::findNextSimilarNote() { // This is implemented in TasEditorWindow.cpp } // ------------------------------------------------------------------------------------ void MARKERS_MANAGER::updateEditedMarkerNote() { if (!markerNoteEditMode) { return; } int len=0; char new_text[MAX_NOTE_LEN]; if (markerNoteEditMode == MARKER_NOTE_EDIT_UPPER) { len = tasWin->upperMarkerNote->text().size(); if ( len >= MAX_NOTE_LEN ) { len = MAX_NOTE_LEN-1; } strncpy( new_text, tasWin->upperMarkerNote->text().toLocal8Bit().constData(), MAX_NOTE_LEN-1 ); new_text[len] = 0; // check changes if (strcmp(getNoteCopy(playback->displayedMarkerNumber).c_str(), new_text)) { setNote(playback->displayedMarkerNumber, new_text); if (playback->displayedMarkerNumber) { history->registerMarkersChange(MODTYPE_MARKER_RENAME, getMarkerFrameNumber(playback->displayedMarkerNumber), -1, new_text); } else { // zeroth Marker - just assume it's set on frame 0 history->registerMarkersChange(MODTYPE_MARKER_RENAME, 0, -1, new_text); } // notify Selection to change text in the lower Marker (in case both are showing same Marker) selection->mustFindCurrentMarker = true; } } else if (markerNoteEditMode == MARKER_NOTE_EDIT_LOWER) { len = tasWin->lowerMarkerNote->text().size(); if ( len >= MAX_NOTE_LEN ) { len = MAX_NOTE_LEN-1; } strncpy( new_text, tasWin->lowerMarkerNote->text().toLocal8Bit().constData(), MAX_NOTE_LEN-1 ); new_text[len] = 0; // check changes if (strcmp(getNoteCopy(selection->displayedMarkerNumber).c_str(), new_text)) { setNote(selection->displayedMarkerNumber, new_text); if (selection->displayedMarkerNumber) { history->registerMarkersChange(MODTYPE_MARKER_RENAME, getMarkerFrameNumber(selection->displayedMarkerNumber), -1, new_text); } else { // zeroth Marker - just assume it's set on frame 0 history->registerMarkersChange(MODTYPE_MARKER_RENAME, 0, -1, new_text); } // notify Playback to change text in upper Marker (in case both are showing same Marker) playback->mustFindCurrentMarker = true; } } } // ------------------------------------------------------------------------------------