/* * PatternFindReplace.cpp * ---------------------- * Purpose: Implementation of the pattern search. * Notes : (currently none) * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Mainfrm.h" #include "Moddoc.h" #include "resource.h" #include "View_pat.h" #include "PatternEditorDialogs.h" #include "PatternFindReplace.h" #include "PatternFindReplaceDlg.h" #include "../soundlib/mod_specifications.h" OPENMPT_NAMESPACE_BEGIN FindReplace FindReplace::instance; FindReplace::FindReplace() : findFlags(FullSearch), replaceFlags(ReplaceAll) , replaceNoteAction(ReplaceValue), replaceInstrAction(ReplaceValue), replaceVolumeAction(ReplaceValue), replaceParamAction(ReplaceValue) , replaceNote(NOTE_NONE), replaceInstr(0), replaceVolume(0), replaceParam(0) , replaceVolCmd(VOLCMD_NONE), replaceCommand(CMD_NONE) , findNoteMin(NOTE_NONE), findNoteMax(NOTE_NONE) , findInstrMin(0), findInstrMax(0) , findVolCmd(VOLCMD_NONE) , findVolumeMin(0), findVolumeMax(0) , findCommand(CMD_NONE) , findParamMin(0), findParamMax(0) , selection(PatternRect()) , findChnMin(0), findChnMax(0) { } void CViewPattern::OnEditFind() { static bool dialogOpen = false; CModDoc *pModDoc = GetDocument(); if (pModDoc && !dialogOpen) { CSoundFile &sndFile = pModDoc->GetSoundFile(); FindReplace settings = FindReplace::instance; ModCommand m = ModCommand::Empty(); if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight()) { settings.findFlags.set(FindReplace::InPatSelection); settings.findFlags.reset(FindReplace::FullSearch); } else if(sndFile.Patterns.IsValidPat(m_nPattern)) { const CPattern &pat = sndFile.Patterns[m_nPattern]; m_Cursor.Sanitize(pat.GetNumRows(), pat.GetNumChannels()); m = *pat.GetpModCommand(m_Cursor.GetRow(), m_Cursor.GetChannel()); } CFindReplaceTab pageFind(IDD_EDIT_FIND, false, sndFile, settings, m); CFindReplaceTab pageReplace(IDD_EDIT_REPLACE, true, sndFile, settings, m); CPropertySheet dlg(_T("Find/Replace")); dlg.AddPage(&pageFind); dlg.AddPage(&pageReplace); dialogOpen = true; if(dlg.DoModal() == IDOK) { FindReplace::instance = settings; FindReplace::instance.selection = m_Selection; m_bContinueSearch = false; OnEditFindNext(); } dialogOpen = false; } } void CViewPattern::OnEditFindNext() { CSoundFile &sndFile = *GetSoundFile(); const CModSpecifications &specs = sndFile.GetModSpecifications(); uint32 nFound = 0; if(!FindReplace::instance.findFlags[~FindReplace::FullSearch]) { PostMessage(WM_COMMAND, ID_EDIT_FIND); return; } BeginWaitCursor(); EffectInfo effectInfo(sndFile); PATTERNINDEX patStart = m_nPattern; PATTERNINDEX patEnd = m_nPattern + 1; if(FindReplace::instance.findFlags[FindReplace::FullSearch]) { patStart = 0; patEnd = sndFile.Patterns.Size(); } else if(FindReplace::instance.findFlags[FindReplace::InPatSelection]) { patStart = m_nPattern; patEnd = patStart + 1; } if(m_bContinueSearch) { patStart = m_nPattern; } // Do we search for an extended effect? bool isExtendedEffect = false; if(FindReplace::instance.findFlags[FindReplace::Command]) { UINT fxndx = effectInfo.GetIndexFromEffect(FindReplace::instance.findCommand, static_cast(FindReplace::instance.findParamMin)); isExtendedEffect = effectInfo.IsExtendedEffect(fxndx); } CHANNELINDEX firstChannel = 0; CHANNELINDEX lastChannel = sndFile.GetNumChannels() - 1; if(FindReplace::instance.findFlags[FindReplace::InChannels]) { // Limit search to given channels firstChannel = std::min(FindReplace::instance.findChnMin, lastChannel); lastChannel = std::min(FindReplace::instance.findChnMax, lastChannel); } if(FindReplace::instance.findFlags[FindReplace::InPatSelection]) { // Limit search to pattern selection firstChannel = std::min(FindReplace::instance.selection.GetStartChannel(), lastChannel); lastChannel = std::min(FindReplace::instance.selection.GetEndChannel(), lastChannel); } for(PATTERNINDEX pat = patStart; pat < patEnd; pat++) { if(!sndFile.Patterns.IsValidPat(pat)) { continue; } ROWINDEX row = 0; CHANNELINDEX chn = firstChannel; if(m_bContinueSearch && pat == patStart && pat == m_nPattern) { // Continue search from cursor position row = GetCurrentRow(); chn = GetCurrentChannel() + 1; if(chn > lastChannel) { row++; chn = firstChannel; } else if(chn < firstChannel) { chn = firstChannel; } } bool firstInPat = true; const ROWINDEX numRows = sndFile.Patterns[pat].GetNumRows(); std::vector lastInstr(sndFile.GetNumChannels(), 0); for(; row < numRows; row++) { ModCommand *m = sndFile.Patterns[pat].GetpModCommand(row, chn); for(; chn <= lastChannel; chn++, m++) { RowMask findWhere; if(FindReplace::instance.findFlags[FindReplace::InPatSelection]) { // Limit search to pattern selection if((chn == FindReplace::instance.selection.GetStartChannel() || chn == FindReplace::instance.selection.GetEndChannel()) && row >= FindReplace::instance.selection.GetStartRow() && row <= FindReplace::instance.selection.GetEndRow()) { // For channels that are on the left and right boundaries of the selection, we need to check // columns are actually selected a bit more thoroughly. for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++) { PatternCursor cursor(row, chn, static_cast(i)); if(!FindReplace::instance.selection.Contains(cursor)) { switch(i) { case PatternCursor::noteColumn: findWhere.note = false; break; case PatternCursor::instrColumn: findWhere.instrument = false; break; case PatternCursor::volumeColumn: findWhere.volume = false; break; case PatternCursor::effectColumn: findWhere.command = false; break; case PatternCursor::paramColumn: findWhere.parameter = false; break; } } } } else { // For channels inside the selection, we have an easier job to solve. if(!FindReplace::instance.selection.Contains(PatternCursor(row, chn))) { findWhere.Clear(); } } } if(m->instr > 0) lastInstr[chn] = m->instr; if((FindReplace::instance.findFlags[FindReplace::Note] && (!findWhere.note || m->note < FindReplace::instance.findNoteMin || m->note > FindReplace::instance.findNoteMax)) || (FindReplace::instance.findFlags[FindReplace::Instr] && (!findWhere.instrument || m->instr < FindReplace::instance.findInstrMin || m->instr > FindReplace::instance.findInstrMax))) { continue; } if(!m->IsPcNote()) { if((FindReplace::instance.findFlags[FindReplace::VolCmd] && (!findWhere.volume || m->volcmd != FindReplace::instance.findVolCmd)) || (FindReplace::instance.findFlags[FindReplace::Volume] && (!findWhere.volume || m->volcmd == VOLCMD_NONE || m->vol < FindReplace::instance.findVolumeMin || m->vol > FindReplace::instance.findVolumeMax)) || (FindReplace::instance.findFlags[FindReplace::Command] && (!findWhere.command || m->command != FindReplace::instance.findCommand)) || (FindReplace::instance.findFlags[FindReplace::Param] && (!findWhere.parameter || m->command == CMD_NONE || m->param < FindReplace::instance.findParamMin || m->param > FindReplace::instance.findParamMax)) || FindReplace::instance.findFlags[FindReplace::PCParam] || FindReplace::instance.findFlags[FindReplace::PCValue]) { continue; } } else { if((FindReplace::instance.findFlags[FindReplace::PCParam] && (!findWhere.volume || m->GetValueVolCol() < FindReplace::instance.findParamMin || m->GetValueVolCol() > FindReplace::instance.findParamMax)) || (FindReplace::instance.findFlags[FindReplace::PCValue] && (!(findWhere.command || findWhere.parameter) || m->GetValueEffectCol() < FindReplace::instance.findVolumeMin || m->GetValueEffectCol() > FindReplace::instance.findVolumeMax)) || FindReplace::instance.findFlags[FindReplace::VolCmd] || FindReplace::instance.findFlags[FindReplace::Volume] || FindReplace::instance.findFlags[FindReplace::Command] || FindReplace::instance.findFlags[FindReplace::Param]) { continue; } } if((FindReplace::instance.findFlags & (FindReplace::Command | FindReplace::Param)) == FindReplace::Command && isExtendedEffect) { if((m->param & 0xF0) != (FindReplace::instance.findParamMin & 0xF0)) continue; } // Found! // Do we want to jump to the finding in this pattern? const bool updatePos = !FindReplace::instance.replaceFlags.test_all(FindReplace::ReplaceAll | FindReplace::Replace); nFound++; if(updatePos) { if(IsLiveRecord()) { // turn off "follow song" m_Status.reset(psFollowSong); SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 0); } // Find sequence and order where this pattern is used const auto numSequences = sndFile.Order.GetNumSequences(); auto seq = sndFile.Order.GetCurrentSequenceIndex(); for(SEQUENCEINDEX i = 0; i < numSequences; i++) { const bool isCurrentSeq = (i == 0); ORDERINDEX matchingOrder = sndFile.Order(seq).FindOrder(pat, isCurrentSeq ? GetCurrentOrder() : 0); if(matchingOrder != ORDERINDEX_INVALID) { if(!isCurrentSeq) SendCtrlMessage(CTRLMSG_PAT_SETSEQUENCE, seq); SetCurrentOrder(matchingOrder); break; } if(++seq >= numSequences) seq = 0; } // go to place of finding SetCurrentPattern(pat); } PatternCursor::Columns foundCol = PatternCursor::firstColumn; if(FindReplace::instance.findFlags[FindReplace::Note]) foundCol = PatternCursor::noteColumn; else if(FindReplace::instance.findFlags[FindReplace::Instr]) foundCol = PatternCursor::instrColumn; else if(FindReplace::instance.findFlags[FindReplace::VolCmd | FindReplace::Volume | FindReplace::PCParam]) foundCol = PatternCursor::volumeColumn; else if(FindReplace::instance.findFlags[FindReplace::Command | FindReplace::PCValue]) foundCol = PatternCursor::effectColumn; else if(FindReplace::instance.findFlags[FindReplace::Param]) foundCol = PatternCursor::paramColumn; if(updatePos) { // Jump to pattern cell SetCursorPosition(PatternCursor(row, chn, foundCol)); } if(!FindReplace::instance.replaceFlags[FindReplace::Replace]) goto EndSearch; bool replace = true; if(!FindReplace::instance.replaceFlags[FindReplace::ReplaceAll]) { ConfirmAnswer result = Reporting::Confirm("Replace this occurrence?", "Replace", true); if(result == cnfCancel) { goto EndSearch; // Yuck! } else { replace = (result == cnfYes); } } if(replace) { if(FindReplace::instance.replaceFlags[FindReplace::ReplaceAll]) { // Just create one logic undo step per pattern when auto-replacing all occurences. if(firstInPat) { GetDocument()->GetPatternUndo().PrepareUndo(pat, firstChannel, row, lastChannel - firstChannel + 1, numRows - row + 1, "Find / Replace", (nFound > 1)); firstInPat = false; } } else { // Create separately undo-able items when replacing manually. GetDocument()->GetPatternUndo().PrepareUndo(pat, chn, row, 1, 1, "Find / Replace"); } if(FindReplace::instance.replaceFlags[FindReplace::Instr]) { int instrReplace = FindReplace::instance.replaceInstr; int instr = m->instr; if(FindReplace::instance.replaceInstrAction == FindReplace::ReplaceRelative && instr > 0) instr += instrReplace; else if(FindReplace::instance.replaceInstrAction == FindReplace::ReplaceValue) instr = instrReplace; m->instr = mpt::saturate_cast(instr); if(m->instr > 0) lastInstr[chn] = m->instr; } if(FindReplace::instance.replaceFlags[FindReplace::Note]) { int noteReplace = FindReplace::instance.replaceNote; if(FindReplace::instance.replaceNoteAction == FindReplace::ReplaceRelative && m->IsNote()) { if(noteReplace == FindReplace::ReplaceOctaveUp || noteReplace == FindReplace::ReplaceOctaveDown) { noteReplace = GetDocument()->GetInstrumentGroupSize(lastInstr[chn]) * mpt::signum(noteReplace); } int note = Clamp(m->note + noteReplace, specs.noteMin, specs.noteMax); m->note = static_cast(note); } else if(FindReplace::instance.replaceNoteAction == FindReplace::ReplaceValue) { // Replace with another note // If we're going to remove a PC Note or replace a normal note by a PC note, wipe out the complete column. if(m->IsPcNote() != ModCommand::IsPcNote(static_cast(noteReplace))) { m->Clear(); } m->note = static_cast(noteReplace); } } bool hadVolume = (m->volcmd == VOLCMD_VOLUME); if(FindReplace::instance.replaceFlags[FindReplace::VolCmd]) { m->volcmd = FindReplace::instance.replaceVolCmd; } if(FindReplace::instance.replaceFlags[FindReplace::Volume]) { int volReplace = FindReplace::instance.replaceVolume; int vol = m->vol; if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative || FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceMultiply) { if(!hadVolume && m->volcmd == VOLCMD_VOLUME) vol = GetDefaultVolume(*m, lastInstr[chn]); if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative) vol += volReplace; else vol = Util::muldivr(vol, volReplace, 100); } else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceValue) { vol = volReplace; } m->vol = mpt::saturate_cast(vol); } if(FindReplace::instance.replaceFlags[FindReplace::VolCmd | FindReplace::Volume] && m->volcmd != VOLCMD_NONE) { // Fix volume command parameters if necessary. This is necesary e.g. // when there was a command "v24" and the user searched for v and replaced it by d. // In that case, d24 wouldn't be a valid command. ModCommand::VOL minVal = 0, maxVal = 64; if(effectInfo.GetVolCmdInfo(effectInfo.GetIndexFromVolCmd(m->volcmd), nullptr, &minVal, &maxVal)) { Limit(m->vol, minVal, maxVal); } } hadVolume = (m->command == CMD_VOLUME); if(FindReplace::instance.replaceFlags[FindReplace::Command]) { m->command = FindReplace::instance.replaceCommand; } if(FindReplace::instance.replaceFlags[FindReplace::Param]) { int paramReplace = FindReplace::instance.replaceParam; int param = m->param; if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative || FindReplace::instance.replaceParamAction == FindReplace::ReplaceMultiply) { if(isExtendedEffect) param &= 0x0F; if(!hadVolume && m->command == CMD_VOLUME) param = GetDefaultVolume(*m, lastInstr[chn]); if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative) param += paramReplace; else param = Util::muldivr(param, paramReplace, 100); if(isExtendedEffect) param = Clamp(param, 0, 15) | (m->param & 0xF0); } else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceValue) { param = paramReplace; } if(isExtendedEffect && !FindReplace::instance.replaceFlags[FindReplace::Command]) m->param = static_cast((m->param & 0xF0) | (param & 0x0F)); else m->param = mpt::saturate_cast(param); } if(FindReplace::instance.replaceFlags[FindReplace::PCParam]) { int paramReplace = FindReplace::instance.replaceParam; int param = m->GetValueVolCol(); if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative) param += paramReplace; else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceMultiply) param = Util::muldivr(param, paramReplace, 100); else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceValue) param = paramReplace; m->SetValueVolCol(static_cast(Clamp(param, 0, ModCommand::maxColumnValue))); } if(FindReplace::instance.replaceFlags[FindReplace::PCValue]) { int valueReplace = FindReplace::instance.replaceVolume; int value = m->GetValueEffectCol(); if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative) value += valueReplace; else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceMultiply) value = Util::muldivr(value, valueReplace, 100); else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceValue) value = valueReplace; m->SetValueEffectCol(static_cast(Clamp(value, 0, ModCommand::maxColumnValue))); } SetModified(false); if(updatePos) InvalidateRow(); } } chn = firstChannel; } } EndSearch: if(FindReplace::instance.replaceFlags[FindReplace::ReplaceAll]) { InvalidatePattern(); } if(FindReplace::instance.findFlags[FindReplace::InPatSelection] && (nFound == 0 || (FindReplace::instance.replaceFlags & (FindReplace::Replace | FindReplace::ReplaceAll)) == FindReplace::Replace)) { // Restore original selection if we didn't find anything or just replaced stuff manually. m_Selection = FindReplace::instance.selection; InvalidatePattern(); } m_bContinueSearch = true; EndWaitCursor(); // Display search results if(nFound == 0) { CString result; result.Preallocate(14 + 16); result = _T("Cannot find \""); // Note if(FindReplace::instance.findFlags[FindReplace::Note]) { result += mpt::ToCString(sndFile.GetNoteName(FindReplace::instance.findNoteMin)); if(FindReplace::instance.findNoteMax > FindReplace::instance.findNoteMin) { result.AppendChar(_T('-')); result += mpt::ToCString(sndFile.GetNoteName(FindReplace::instance.findNoteMax)); } } else { result += _T("???"); } result.AppendChar(_T(' ')); // Instrument if(FindReplace::instance.findFlags[FindReplace::Instr]) { if(FindReplace::instance.findInstrMin) result.AppendFormat(_T("%03d"), FindReplace::instance.findInstrMin); else result.Append(_T(" ..")); if(FindReplace::instance.findInstrMax > FindReplace::instance.findInstrMin) result.AppendFormat(_T("-%03d"), FindReplace::instance.findInstrMax); } else { result.Append(_T(" ??")); } result.AppendChar(_T(' ')); // Volume Command if(FindReplace::instance.findFlags[FindReplace::VolCmd]) { if(FindReplace::instance.findVolCmd != VOLCMD_NONE) result.AppendChar(specs.GetVolEffectLetter(FindReplace::instance.findVolCmd)); else result.AppendChar(_T('.')); } else if(FindReplace::instance.findFlags[FindReplace::PCParam]) { result.AppendFormat(_T("%03d"), FindReplace::instance.findParamMin); if(FindReplace::instance.findParamMax > FindReplace::instance.findParamMin) result.AppendFormat(_T("-%03d"), FindReplace::instance.findParamMax); } else { result.AppendChar(_T('?')); } // Volume Parameter if(FindReplace::instance.findFlags[FindReplace::Volume]) { result.AppendFormat(_T("%02d"), FindReplace::instance.findVolumeMin); if(FindReplace::instance.findVolumeMax > FindReplace::instance.findVolumeMin) result.AppendFormat(_T("-%02d"), FindReplace::instance.findVolumeMax); } else if(!FindReplace::instance.findFlags[FindReplace::PCParam]) { result.AppendFormat(_T("??")); } result.AppendChar(_T(' ')); // Effect Command if(FindReplace::instance.findFlags[FindReplace::Command]) { if(FindReplace::instance.findCommand != CMD_NONE) result.AppendChar(specs.GetEffectLetter(FindReplace::instance.findCommand)); else result.AppendChar(_T('.')); } else if(FindReplace::instance.findFlags[FindReplace::PCValue]) { result.AppendFormat(_T("%03d"), FindReplace::instance.findVolumeMin); if(FindReplace::instance.findVolumeMax > FindReplace::instance.findVolumeMin) result.AppendFormat(_T("-%03d"), FindReplace::instance.findVolumeMax); } else { result.AppendChar(_T('?')); } // Effect Parameter if(FindReplace::instance.findFlags[FindReplace::Param]) { result.AppendFormat(_T("%02X"), FindReplace::instance.findParamMin); if(FindReplace::instance.findParamMax > FindReplace::instance.findParamMin) result.AppendFormat(_T("-%02X"), FindReplace::instance.findParamMax); } else if(!FindReplace::instance.findFlags[FindReplace::PCValue]) { result.AppendFormat(_T("??")); } result.AppendChar(_T('"')); Reporting::Information(result, _T("Find/Replace")); } } OPENMPT_NAMESPACE_END