PatternFindReplace.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /*
  2. * PatternFindReplace.cpp
  3. * ----------------------
  4. * Purpose: Implementation of the pattern search.
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Mainfrm.h"
  12. #include "Moddoc.h"
  13. #include "resource.h"
  14. #include "View_pat.h"
  15. #include "PatternEditorDialogs.h"
  16. #include "PatternFindReplace.h"
  17. #include "PatternFindReplaceDlg.h"
  18. #include "../soundlib/mod_specifications.h"
  19. OPENMPT_NAMESPACE_BEGIN
  20. FindReplace FindReplace::instance;
  21. FindReplace::FindReplace()
  22. : findFlags(FullSearch), replaceFlags(ReplaceAll)
  23. , replaceNoteAction(ReplaceValue), replaceInstrAction(ReplaceValue), replaceVolumeAction(ReplaceValue), replaceParamAction(ReplaceValue)
  24. , replaceNote(NOTE_NONE), replaceInstr(0), replaceVolume(0), replaceParam(0)
  25. , replaceVolCmd(VOLCMD_NONE), replaceCommand(CMD_NONE)
  26. , findNoteMin(NOTE_NONE), findNoteMax(NOTE_NONE)
  27. , findInstrMin(0), findInstrMax(0)
  28. , findVolCmd(VOLCMD_NONE)
  29. , findVolumeMin(0), findVolumeMax(0)
  30. , findCommand(CMD_NONE)
  31. , findParamMin(0), findParamMax(0)
  32. , selection(PatternRect())
  33. , findChnMin(0), findChnMax(0)
  34. { }
  35. void CViewPattern::OnEditFind()
  36. {
  37. static bool dialogOpen = false;
  38. CModDoc *pModDoc = GetDocument();
  39. if (pModDoc && !dialogOpen)
  40. {
  41. CSoundFile &sndFile = pModDoc->GetSoundFile();
  42. FindReplace settings = FindReplace::instance;
  43. ModCommand m = ModCommand::Empty();
  44. if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
  45. {
  46. settings.findFlags.set(FindReplace::InPatSelection);
  47. settings.findFlags.reset(FindReplace::FullSearch);
  48. } else if(sndFile.Patterns.IsValidPat(m_nPattern))
  49. {
  50. const CPattern &pat = sndFile.Patterns[m_nPattern];
  51. m_Cursor.Sanitize(pat.GetNumRows(), pat.GetNumChannels());
  52. m = *pat.GetpModCommand(m_Cursor.GetRow(), m_Cursor.GetChannel());
  53. }
  54. CFindReplaceTab pageFind(IDD_EDIT_FIND, false, sndFile, settings, m);
  55. CFindReplaceTab pageReplace(IDD_EDIT_REPLACE, true, sndFile, settings, m);
  56. CPropertySheet dlg(_T("Find/Replace"));
  57. dlg.AddPage(&pageFind);
  58. dlg.AddPage(&pageReplace);
  59. dialogOpen = true;
  60. if(dlg.DoModal() == IDOK)
  61. {
  62. FindReplace::instance = settings;
  63. FindReplace::instance.selection = m_Selection;
  64. m_bContinueSearch = false;
  65. OnEditFindNext();
  66. }
  67. dialogOpen = false;
  68. }
  69. }
  70. void CViewPattern::OnEditFindNext()
  71. {
  72. CSoundFile &sndFile = *GetSoundFile();
  73. const CModSpecifications &specs = sndFile.GetModSpecifications();
  74. uint32 nFound = 0;
  75. if(!FindReplace::instance.findFlags[~FindReplace::FullSearch])
  76. {
  77. PostMessage(WM_COMMAND, ID_EDIT_FIND);
  78. return;
  79. }
  80. BeginWaitCursor();
  81. EffectInfo effectInfo(sndFile);
  82. PATTERNINDEX patStart = m_nPattern;
  83. PATTERNINDEX patEnd = m_nPattern + 1;
  84. if(FindReplace::instance.findFlags[FindReplace::FullSearch])
  85. {
  86. patStart = 0;
  87. patEnd = sndFile.Patterns.Size();
  88. } else if(FindReplace::instance.findFlags[FindReplace::InPatSelection])
  89. {
  90. patStart = m_nPattern;
  91. patEnd = patStart + 1;
  92. }
  93. if(m_bContinueSearch)
  94. {
  95. patStart = m_nPattern;
  96. }
  97. // Do we search for an extended effect?
  98. bool isExtendedEffect = false;
  99. if(FindReplace::instance.findFlags[FindReplace::Command])
  100. {
  101. UINT fxndx = effectInfo.GetIndexFromEffect(FindReplace::instance.findCommand, static_cast<ModCommand::PARAM>(FindReplace::instance.findParamMin));
  102. isExtendedEffect = effectInfo.IsExtendedEffect(fxndx);
  103. }
  104. CHANNELINDEX firstChannel = 0;
  105. CHANNELINDEX lastChannel = sndFile.GetNumChannels() - 1;
  106. if(FindReplace::instance.findFlags[FindReplace::InChannels])
  107. {
  108. // Limit search to given channels
  109. firstChannel = std::min(FindReplace::instance.findChnMin, lastChannel);
  110. lastChannel = std::min(FindReplace::instance.findChnMax, lastChannel);
  111. }
  112. if(FindReplace::instance.findFlags[FindReplace::InPatSelection])
  113. {
  114. // Limit search to pattern selection
  115. firstChannel = std::min(FindReplace::instance.selection.GetStartChannel(), lastChannel);
  116. lastChannel = std::min(FindReplace::instance.selection.GetEndChannel(), lastChannel);
  117. }
  118. for(PATTERNINDEX pat = patStart; pat < patEnd; pat++)
  119. {
  120. if(!sndFile.Patterns.IsValidPat(pat))
  121. {
  122. continue;
  123. }
  124. ROWINDEX row = 0;
  125. CHANNELINDEX chn = firstChannel;
  126. if(m_bContinueSearch && pat == patStart && pat == m_nPattern)
  127. {
  128. // Continue search from cursor position
  129. row = GetCurrentRow();
  130. chn = GetCurrentChannel() + 1;
  131. if(chn > lastChannel)
  132. {
  133. row++;
  134. chn = firstChannel;
  135. } else if(chn < firstChannel)
  136. {
  137. chn = firstChannel;
  138. }
  139. }
  140. bool firstInPat = true;
  141. const ROWINDEX numRows = sndFile.Patterns[pat].GetNumRows();
  142. std::vector<ModCommand::INSTR> lastInstr(sndFile.GetNumChannels(), 0);
  143. for(; row < numRows; row++)
  144. {
  145. ModCommand *m = sndFile.Patterns[pat].GetpModCommand(row, chn);
  146. for(; chn <= lastChannel; chn++, m++)
  147. {
  148. RowMask findWhere;
  149. if(FindReplace::instance.findFlags[FindReplace::InPatSelection])
  150. {
  151. // Limit search to pattern selection
  152. if((chn == FindReplace::instance.selection.GetStartChannel() || chn == FindReplace::instance.selection.GetEndChannel())
  153. && row >= FindReplace::instance.selection.GetStartRow() && row <= FindReplace::instance.selection.GetEndRow())
  154. {
  155. // For channels that are on the left and right boundaries of the selection, we need to check
  156. // columns are actually selected a bit more thoroughly.
  157. for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
  158. {
  159. PatternCursor cursor(row, chn, static_cast<PatternCursor::Columns>(i));
  160. if(!FindReplace::instance.selection.Contains(cursor))
  161. {
  162. switch(i)
  163. {
  164. case PatternCursor::noteColumn: findWhere.note = false; break;
  165. case PatternCursor::instrColumn: findWhere.instrument = false; break;
  166. case PatternCursor::volumeColumn: findWhere.volume = false; break;
  167. case PatternCursor::effectColumn: findWhere.command = false; break;
  168. case PatternCursor::paramColumn: findWhere.parameter = false; break;
  169. }
  170. }
  171. }
  172. } else
  173. {
  174. // For channels inside the selection, we have an easier job to solve.
  175. if(!FindReplace::instance.selection.Contains(PatternCursor(row, chn)))
  176. {
  177. findWhere.Clear();
  178. }
  179. }
  180. }
  181. if(m->instr > 0)
  182. lastInstr[chn] = m->instr;
  183. if((FindReplace::instance.findFlags[FindReplace::Note] && (!findWhere.note || m->note < FindReplace::instance.findNoteMin || m->note > FindReplace::instance.findNoteMax))
  184. || (FindReplace::instance.findFlags[FindReplace::Instr] && (!findWhere.instrument || m->instr < FindReplace::instance.findInstrMin || m->instr > FindReplace::instance.findInstrMax)))
  185. {
  186. continue;
  187. }
  188. if(!m->IsPcNote())
  189. {
  190. if((FindReplace::instance.findFlags[FindReplace::VolCmd] && (!findWhere.volume || m->volcmd != FindReplace::instance.findVolCmd))
  191. || (FindReplace::instance.findFlags[FindReplace::Volume] && (!findWhere.volume || m->volcmd == VOLCMD_NONE || m->vol < FindReplace::instance.findVolumeMin || m->vol > FindReplace::instance.findVolumeMax))
  192. || (FindReplace::instance.findFlags[FindReplace::Command] && (!findWhere.command || m->command != FindReplace::instance.findCommand))
  193. || (FindReplace::instance.findFlags[FindReplace::Param] && (!findWhere.parameter || m->command == CMD_NONE || m->param < FindReplace::instance.findParamMin || m->param > FindReplace::instance.findParamMax))
  194. || FindReplace::instance.findFlags[FindReplace::PCParam]
  195. || FindReplace::instance.findFlags[FindReplace::PCValue])
  196. {
  197. continue;
  198. }
  199. } else
  200. {
  201. if((FindReplace::instance.findFlags[FindReplace::PCParam] && (!findWhere.volume || m->GetValueVolCol() < FindReplace::instance.findParamMin || m->GetValueVolCol() > FindReplace::instance.findParamMax))
  202. || (FindReplace::instance.findFlags[FindReplace::PCValue] && (!(findWhere.command || findWhere.parameter) || m->GetValueEffectCol() < FindReplace::instance.findVolumeMin || m->GetValueEffectCol() > FindReplace::instance.findVolumeMax))
  203. || FindReplace::instance.findFlags[FindReplace::VolCmd]
  204. || FindReplace::instance.findFlags[FindReplace::Volume]
  205. || FindReplace::instance.findFlags[FindReplace::Command]
  206. || FindReplace::instance.findFlags[FindReplace::Param])
  207. {
  208. continue;
  209. }
  210. }
  211. if((FindReplace::instance.findFlags & (FindReplace::Command | FindReplace::Param)) == FindReplace::Command && isExtendedEffect)
  212. {
  213. if((m->param & 0xF0) != (FindReplace::instance.findParamMin & 0xF0))
  214. continue;
  215. }
  216. // Found!
  217. // Do we want to jump to the finding in this pattern?
  218. const bool updatePos = !FindReplace::instance.replaceFlags.test_all(FindReplace::ReplaceAll | FindReplace::Replace);
  219. nFound++;
  220. if(updatePos)
  221. {
  222. if(IsLiveRecord())
  223. {
  224. // turn off "follow song"
  225. m_Status.reset(psFollowSong);
  226. SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 0);
  227. }
  228. // Find sequence and order where this pattern is used
  229. const auto numSequences = sndFile.Order.GetNumSequences();
  230. auto seq = sndFile.Order.GetCurrentSequenceIndex();
  231. for(SEQUENCEINDEX i = 0; i < numSequences; i++)
  232. {
  233. const bool isCurrentSeq = (i == 0);
  234. ORDERINDEX matchingOrder = sndFile.Order(seq).FindOrder(pat, isCurrentSeq ? GetCurrentOrder() : 0);
  235. if(matchingOrder != ORDERINDEX_INVALID)
  236. {
  237. if(!isCurrentSeq)
  238. SendCtrlMessage(CTRLMSG_PAT_SETSEQUENCE, seq);
  239. SetCurrentOrder(matchingOrder);
  240. break;
  241. }
  242. if(++seq >= numSequences)
  243. seq = 0;
  244. }
  245. // go to place of finding
  246. SetCurrentPattern(pat);
  247. }
  248. PatternCursor::Columns foundCol = PatternCursor::firstColumn;
  249. if(FindReplace::instance.findFlags[FindReplace::Note])
  250. foundCol = PatternCursor::noteColumn;
  251. else if(FindReplace::instance.findFlags[FindReplace::Instr])
  252. foundCol = PatternCursor::instrColumn;
  253. else if(FindReplace::instance.findFlags[FindReplace::VolCmd | FindReplace::Volume | FindReplace::PCParam])
  254. foundCol = PatternCursor::volumeColumn;
  255. else if(FindReplace::instance.findFlags[FindReplace::Command | FindReplace::PCValue])
  256. foundCol = PatternCursor::effectColumn;
  257. else if(FindReplace::instance.findFlags[FindReplace::Param])
  258. foundCol = PatternCursor::paramColumn;
  259. if(updatePos)
  260. {
  261. // Jump to pattern cell
  262. SetCursorPosition(PatternCursor(row, chn, foundCol));
  263. }
  264. if(!FindReplace::instance.replaceFlags[FindReplace::Replace]) goto EndSearch;
  265. bool replace = true;
  266. if(!FindReplace::instance.replaceFlags[FindReplace::ReplaceAll])
  267. {
  268. ConfirmAnswer result = Reporting::Confirm("Replace this occurrence?", "Replace", true);
  269. if(result == cnfCancel)
  270. {
  271. goto EndSearch; // Yuck!
  272. } else
  273. {
  274. replace = (result == cnfYes);
  275. }
  276. }
  277. if(replace)
  278. {
  279. if(FindReplace::instance.replaceFlags[FindReplace::ReplaceAll])
  280. {
  281. // Just create one logic undo step per pattern when auto-replacing all occurences.
  282. if(firstInPat)
  283. {
  284. GetDocument()->GetPatternUndo().PrepareUndo(pat, firstChannel, row, lastChannel - firstChannel + 1, numRows - row + 1, "Find / Replace", (nFound > 1));
  285. firstInPat = false;
  286. }
  287. } else
  288. {
  289. // Create separately undo-able items when replacing manually.
  290. GetDocument()->GetPatternUndo().PrepareUndo(pat, chn, row, 1, 1, "Find / Replace");
  291. }
  292. if(FindReplace::instance.replaceFlags[FindReplace::Instr])
  293. {
  294. int instrReplace = FindReplace::instance.replaceInstr;
  295. int instr = m->instr;
  296. if(FindReplace::instance.replaceInstrAction == FindReplace::ReplaceRelative && instr > 0)
  297. instr += instrReplace;
  298. else if(FindReplace::instance.replaceInstrAction == FindReplace::ReplaceValue)
  299. instr = instrReplace;
  300. m->instr = mpt::saturate_cast<ModCommand::INSTR>(instr);
  301. if(m->instr > 0)
  302. lastInstr[chn] = m->instr;
  303. }
  304. if(FindReplace::instance.replaceFlags[FindReplace::Note])
  305. {
  306. int noteReplace = FindReplace::instance.replaceNote;
  307. if(FindReplace::instance.replaceNoteAction == FindReplace::ReplaceRelative && m->IsNote())
  308. {
  309. if(noteReplace == FindReplace::ReplaceOctaveUp || noteReplace == FindReplace::ReplaceOctaveDown)
  310. {
  311. noteReplace = GetDocument()->GetInstrumentGroupSize(lastInstr[chn]) * mpt::signum(noteReplace);
  312. }
  313. int note = Clamp(m->note + noteReplace, specs.noteMin, specs.noteMax);
  314. m->note = static_cast<ModCommand::NOTE>(note);
  315. } else if(FindReplace::instance.replaceNoteAction == FindReplace::ReplaceValue)
  316. {
  317. // Replace with another note
  318. // If we're going to remove a PC Note or replace a normal note by a PC note, wipe out the complete column.
  319. if(m->IsPcNote() != ModCommand::IsPcNote(static_cast<ModCommand::NOTE>(noteReplace)))
  320. {
  321. m->Clear();
  322. }
  323. m->note = static_cast<ModCommand::NOTE>(noteReplace);
  324. }
  325. }
  326. bool hadVolume = (m->volcmd == VOLCMD_VOLUME);
  327. if(FindReplace::instance.replaceFlags[FindReplace::VolCmd])
  328. {
  329. m->volcmd = FindReplace::instance.replaceVolCmd;
  330. }
  331. if(FindReplace::instance.replaceFlags[FindReplace::Volume])
  332. {
  333. int volReplace = FindReplace::instance.replaceVolume;
  334. int vol = m->vol;
  335. if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative || FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceMultiply)
  336. {
  337. if(!hadVolume && m->volcmd == VOLCMD_VOLUME)
  338. vol = GetDefaultVolume(*m, lastInstr[chn]);
  339. if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative)
  340. vol += volReplace;
  341. else
  342. vol = Util::muldivr(vol, volReplace, 100);
  343. } else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceValue)
  344. {
  345. vol = volReplace;
  346. }
  347. m->vol = mpt::saturate_cast<ModCommand::VOL>(vol);
  348. }
  349. if(FindReplace::instance.replaceFlags[FindReplace::VolCmd | FindReplace::Volume] && m->volcmd != VOLCMD_NONE)
  350. {
  351. // Fix volume command parameters if necessary. This is necesary e.g.
  352. // when there was a command "v24" and the user searched for v and replaced it by d.
  353. // In that case, d24 wouldn't be a valid command.
  354. ModCommand::VOL minVal = 0, maxVal = 64;
  355. if(effectInfo.GetVolCmdInfo(effectInfo.GetIndexFromVolCmd(m->volcmd), nullptr, &minVal, &maxVal))
  356. {
  357. Limit(m->vol, minVal, maxVal);
  358. }
  359. }
  360. hadVolume = (m->command == CMD_VOLUME);
  361. if(FindReplace::instance.replaceFlags[FindReplace::Command])
  362. {
  363. m->command = FindReplace::instance.replaceCommand;
  364. }
  365. if(FindReplace::instance.replaceFlags[FindReplace::Param])
  366. {
  367. int paramReplace = FindReplace::instance.replaceParam;
  368. int param = m->param;
  369. if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative || FindReplace::instance.replaceParamAction == FindReplace::ReplaceMultiply)
  370. {
  371. if(isExtendedEffect)
  372. param &= 0x0F;
  373. if(!hadVolume && m->command == CMD_VOLUME)
  374. param = GetDefaultVolume(*m, lastInstr[chn]);
  375. if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative)
  376. param += paramReplace;
  377. else
  378. param = Util::muldivr(param, paramReplace, 100);
  379. if(isExtendedEffect)
  380. param = Clamp(param, 0, 15) | (m->param & 0xF0);
  381. } else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceValue)
  382. {
  383. param = paramReplace;
  384. }
  385. if(isExtendedEffect && !FindReplace::instance.replaceFlags[FindReplace::Command])
  386. m->param = static_cast<ModCommand::PARAM>((m->param & 0xF0) | (param & 0x0F));
  387. else
  388. m->param = mpt::saturate_cast<ModCommand::PARAM>(param);
  389. }
  390. if(FindReplace::instance.replaceFlags[FindReplace::PCParam])
  391. {
  392. int paramReplace = FindReplace::instance.replaceParam;
  393. int param = m->GetValueVolCol();
  394. if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceRelative)
  395. param += paramReplace;
  396. else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceMultiply)
  397. param = Util::muldivr(param, paramReplace, 100);
  398. else if(FindReplace::instance.replaceParamAction == FindReplace::ReplaceValue)
  399. param = paramReplace;
  400. m->SetValueVolCol(static_cast<uint16>(Clamp(param, 0, ModCommand::maxColumnValue)));
  401. }
  402. if(FindReplace::instance.replaceFlags[FindReplace::PCValue])
  403. {
  404. int valueReplace = FindReplace::instance.replaceVolume;
  405. int value = m->GetValueEffectCol();
  406. if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceRelative)
  407. value += valueReplace;
  408. else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceMultiply)
  409. value = Util::muldivr(value, valueReplace, 100);
  410. else if(FindReplace::instance.replaceVolumeAction == FindReplace::ReplaceValue)
  411. value = valueReplace;
  412. m->SetValueEffectCol(static_cast<uint16>(Clamp(value, 0, ModCommand::maxColumnValue)));
  413. }
  414. SetModified(false);
  415. if(updatePos)
  416. InvalidateRow();
  417. }
  418. }
  419. chn = firstChannel;
  420. }
  421. }
  422. EndSearch:
  423. if(FindReplace::instance.replaceFlags[FindReplace::ReplaceAll])
  424. {
  425. InvalidatePattern();
  426. }
  427. if(FindReplace::instance.findFlags[FindReplace::InPatSelection] && (nFound == 0 || (FindReplace::instance.replaceFlags & (FindReplace::Replace | FindReplace::ReplaceAll)) == FindReplace::Replace))
  428. {
  429. // Restore original selection if we didn't find anything or just replaced stuff manually.
  430. m_Selection = FindReplace::instance.selection;
  431. InvalidatePattern();
  432. }
  433. m_bContinueSearch = true;
  434. EndWaitCursor();
  435. // Display search results
  436. if(nFound == 0)
  437. {
  438. CString result;
  439. result.Preallocate(14 + 16);
  440. result = _T("Cannot find \"");
  441. // Note
  442. if(FindReplace::instance.findFlags[FindReplace::Note])
  443. {
  444. result += mpt::ToCString(sndFile.GetNoteName(FindReplace::instance.findNoteMin));
  445. if(FindReplace::instance.findNoteMax > FindReplace::instance.findNoteMin)
  446. {
  447. result.AppendChar(_T('-'));
  448. result += mpt::ToCString(sndFile.GetNoteName(FindReplace::instance.findNoteMax));
  449. }
  450. } else
  451. {
  452. result += _T("???");
  453. }
  454. result.AppendChar(_T(' '));
  455. // Instrument
  456. if(FindReplace::instance.findFlags[FindReplace::Instr])
  457. {
  458. if(FindReplace::instance.findInstrMin)
  459. result.AppendFormat(_T("%03d"), FindReplace::instance.findInstrMin);
  460. else
  461. result.Append(_T(" .."));
  462. if(FindReplace::instance.findInstrMax > FindReplace::instance.findInstrMin)
  463. result.AppendFormat(_T("-%03d"), FindReplace::instance.findInstrMax);
  464. } else
  465. {
  466. result.Append(_T(" ??"));
  467. }
  468. result.AppendChar(_T(' '));
  469. // Volume Command
  470. if(FindReplace::instance.findFlags[FindReplace::VolCmd])
  471. {
  472. if(FindReplace::instance.findVolCmd != VOLCMD_NONE)
  473. result.AppendChar(specs.GetVolEffectLetter(FindReplace::instance.findVolCmd));
  474. else
  475. result.AppendChar(_T('.'));
  476. } else if(FindReplace::instance.findFlags[FindReplace::PCParam])
  477. {
  478. result.AppendFormat(_T("%03d"), FindReplace::instance.findParamMin);
  479. if(FindReplace::instance.findParamMax > FindReplace::instance.findParamMin)
  480. result.AppendFormat(_T("-%03d"), FindReplace::instance.findParamMax);
  481. } else
  482. {
  483. result.AppendChar(_T('?'));
  484. }
  485. // Volume Parameter
  486. if(FindReplace::instance.findFlags[FindReplace::Volume])
  487. {
  488. result.AppendFormat(_T("%02d"), FindReplace::instance.findVolumeMin);
  489. if(FindReplace::instance.findVolumeMax > FindReplace::instance.findVolumeMin)
  490. result.AppendFormat(_T("-%02d"), FindReplace::instance.findVolumeMax);
  491. } else if(!FindReplace::instance.findFlags[FindReplace::PCParam])
  492. {
  493. result.AppendFormat(_T("??"));
  494. }
  495. result.AppendChar(_T(' '));
  496. // Effect Command
  497. if(FindReplace::instance.findFlags[FindReplace::Command])
  498. {
  499. if(FindReplace::instance.findCommand != CMD_NONE)
  500. result.AppendChar(specs.GetEffectLetter(FindReplace::instance.findCommand));
  501. else
  502. result.AppendChar(_T('.'));
  503. } else if(FindReplace::instance.findFlags[FindReplace::PCValue])
  504. {
  505. result.AppendFormat(_T("%03d"), FindReplace::instance.findVolumeMin);
  506. if(FindReplace::instance.findVolumeMax > FindReplace::instance.findVolumeMin)
  507. result.AppendFormat(_T("-%03d"), FindReplace::instance.findVolumeMax);
  508. } else
  509. {
  510. result.AppendChar(_T('?'));
  511. }
  512. // Effect Parameter
  513. if(FindReplace::instance.findFlags[FindReplace::Param])
  514. {
  515. result.AppendFormat(_T("%02X"), FindReplace::instance.findParamMin);
  516. if(FindReplace::instance.findParamMax > FindReplace::instance.findParamMin)
  517. result.AppendFormat(_T("-%02X"), FindReplace::instance.findParamMax);
  518. } else if(!FindReplace::instance.findFlags[FindReplace::PCValue])
  519. {
  520. result.AppendFormat(_T("??"));
  521. }
  522. result.AppendChar(_T('"'));
  523. Reporting::Information(result, _T("Find/Replace"));
  524. }
  525. }
  526. OPENMPT_NAMESPACE_END