123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227 |
- /*
- * PatternClipboard.cpp
- * --------------------
- * Purpose: Implementation of the pattern clipboard mechanism
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "PatternClipboard.h"
- #include "PatternCursor.h"
- #include "Mainfrm.h"
- #include "Moddoc.h"
- #include "Clipboard.h"
- #include "View_pat.h"
- #include "../soundlib/mod_specifications.h"
- #include "../soundlib/Tables.h"
- OPENMPT_NAMESPACE_BEGIN
- /* Clipboard format:
- * Hdr: "ModPlug Tracker S3M\r\n"
- * Full: '|C#401v64A06'
- * Reset: '|...........'
- * Empty: '| '
- * End of row: '\r\n'
- *
- * When pasting multiple patterns, the header line is followed by the order list:
- * Orders: 0,1,2,+,-,1\r\n
- * After that, the individual pattern headers and pattern data follows:
- * 'Rows: 64\r\n' (must be first)
- * 'Name: Pattern Name\r\n' (optional)
- * 'Signature: 4/16\r\n' (optional)
- * 'Swing: 16777216,16777216,16777216,16777216\r\n' (optional)
- * Pattern data...
- */
- PatternClipboard PatternClipboard::instance;
- std::string PatternClipboard::GetFileExtension(const char *ext, bool addPadding)
- {
- std::string format(ext);
- if(format.size() > 3)
- {
- format.resize(3);
- }
- format = mpt::ToUpperCaseAscii(format);
- if(addPadding)
- {
- format.insert(0, 3 - format.size(), ' ');
- }
- return format;
- }
- std::string PatternClipboard::FormatClipboardHeader(const CSoundFile &sndFile)
- {
- return "ModPlug Tracker " + GetFileExtension(sndFile.GetModSpecifications().fileExtension, true) + "\r\n";
- }
- // Copy a range of patterns to both the system clipboard and the internal clipboard.
- bool PatternClipboard::Copy(const CSoundFile &sndFile, ORDERINDEX first, ORDERINDEX last, bool onlyOrders)
- {
- const ModSequence &order = sndFile.Order();
- LimitMax(first, order.GetLength());
- LimitMax(last, order.GetLength());
- // Set up clipboard header.
- std::string data = FormatClipboardHeader(sndFile) + "Orders: ";
- std::string patternData;
- // Pattern => Order list assignment
- std::vector<PATTERNINDEX> patList(sndFile.Patterns.Size(), PATTERNINDEX_INVALID);
- PATTERNINDEX insertedPats = 0;
- // Add order list and pattern information to header.
- for(ORDERINDEX ord = first; ord <= last; ord++)
- {
- PATTERNINDEX pattern = order[ord];
- if(ord != first)
- data += ',';
-
- if(pattern == order.GetInvalidPatIndex())
- {
- data += '-';
- } else if(pattern == order.GetIgnoreIndex())
- {
- data += '+';
- } else if(sndFile.Patterns.IsValidPat(pattern))
- {
- if(onlyOrders)
- {
- patList[pattern] = pattern;
- } else if(patList[pattern] == PATTERNINDEX_INVALID)
- {
- // New pattern
- patList[pattern] = insertedPats++;
- const CPattern &pat = sndFile.Patterns[pattern];
- patternData += MPT_AFORMAT("Rows: {}\r\n")(pat.GetNumRows());
- std::string name = pat.GetName();
- if(!name.empty())
- {
- patternData += "Name: " + name + "\r\n";
- }
- if(pat.GetOverrideSignature())
- {
- patternData += MPT_AFORMAT("Signature: {}/{}\r\n")(pat.GetRowsPerBeat(), pat.GetRowsPerMeasure());
- }
- if(pat.HasTempoSwing())
- {
- patternData += "Swing: ";
- const TempoSwing &swing = pat.GetTempoSwing();
- for(size_t i = 0; i < swing.size(); i++)
- {
- if(i == 0)
- {
- patternData += MPT_AFORMAT("{}")(swing[i]);
- } else
- {
- patternData += MPT_AFORMAT(",{}")(swing[i]);
- }
- }
- patternData += "\r\n";
- }
- patternData += CreateClipboardString(sndFile, pattern, PatternRect(PatternCursor(), PatternCursor(sndFile.Patterns[pattern].GetNumRows() - 1, sndFile.GetNumChannels() - 1, PatternCursor::lastColumn)));
- }
- data += mpt::afmt::val(patList[pattern]);
- }
- }
- if(!onlyOrders)
- {
- data += "\r\n" + patternData;
- }
-
- if(instance.m_activeClipboard < instance.m_clipboards.size())
- {
- // Copy to internal clipboard
- CString desc = MPT_CFORMAT("{} {} ({} to {})")(last - first + 1, onlyOrders ? CString(_T("Orders")) : CString(_T("Patterns")), first, last);
- instance.m_clipboards[instance.m_activeClipboard] = {data, desc};
- }
- return ToSystemClipboard(data);
- }
- // Copy a pattern selection to both the system clipboard and the internal clipboard.
- bool PatternClipboard::Copy(const CSoundFile &sndFile, PATTERNINDEX pattern, PatternRect selection)
- {
- std::string data = CreateClipboardString(sndFile, pattern, selection);
- if(data.empty())
- return false;
- // Set up clipboard header
- data.insert(0, FormatClipboardHeader(sndFile));
- if(instance.m_activeClipboard < instance.m_clipboards.size())
- {
- // Copy to internal clipboard
- CString desc;
- desc.Format(_T("%u rows, %u channels (pattern %u)"), selection.GetNumRows(), selection.GetNumChannels(), pattern);
- instance.m_clipboards[instance.m_activeClipboard] = {data, desc};
- }
- return ToSystemClipboard(data);
- }
- // Copy a pattern or pattern channel to the internal pattern or channel clipboard.
- bool PatternClipboard::Copy(const CSoundFile &sndFile, PATTERNINDEX pattern, CHANNELINDEX channel)
- {
- if(!sndFile.Patterns.IsValidPat(pattern))
- return false;
- const bool patternCopy = (channel == CHANNELINDEX_INVALID);
- const CPattern &pat = sndFile.Patterns[pattern];
- PatternRect selection;
- if(patternCopy)
- selection = {PatternCursor(0, 0, PatternCursor::firstColumn), PatternCursor(pat.GetNumRows() - 1, pat.GetNumChannels() - 1, PatternCursor::lastColumn)};
- else
- selection = {PatternCursor(0, channel, PatternCursor::firstColumn), PatternCursor(pat.GetNumRows() - 1, channel, PatternCursor::lastColumn)};
- std::string data = CreateClipboardString(sndFile, pattern, selection);
- if(data.empty())
- return false;
- // Set up clipboard header
- data.insert(0, FormatClipboardHeader(sndFile));
- // Copy to internal clipboard
- (patternCopy ? instance.m_patternClipboard : instance.m_channelClipboard) = {data, {}};
- return true;
- }
- // Create the clipboard text for a pattern selection
- std::string PatternClipboard::CreateClipboardString(const CSoundFile &sndFile, PATTERNINDEX pattern, PatternRect selection)
- {
- if(!sndFile.Patterns.IsValidPat(pattern))
- return "";
- if(selection.GetStartColumn() == PatternCursor::paramColumn)
- {
- // Special case: If selection starts with a parameter column, extend it to include the effect letter as well.
- PatternCursor upper(selection.GetUpperLeft());
- upper.Move(0, 0, -1);
- selection = PatternRect(upper, selection.GetLowerRight());
- }
- const ROWINDEX startRow = selection.GetStartRow(), numRows = selection.GetNumRows();
- const CHANNELINDEX startChan = selection.GetStartChannel(), numChans = selection.GetNumChannels();
- std::string data;
- data.reserve(numRows * (numChans * 12 + 2));
- for(ROWINDEX row = 0; row < numRows; row++)
- {
- if(row + startRow >= sndFile.Patterns[pattern].GetNumRows())
- break;
- const ModCommand *m = sndFile.Patterns[pattern].GetpModCommand(row + startRow, startChan);
- for(CHANNELINDEX chn = 0; chn < numChans; chn++, m++)
- {
- PatternCursor cursor(0, startChan + chn);
- data += '|';
- // Note
- if(selection.ContainsHorizontal(cursor))
- {
- if(m->IsNote())
- {
- // Need to guarantee that sharps are used for the clipboard.
- data += mpt::ToCharset(mpt::Charset::Locale, mpt::ustring(NoteNamesSharp[(m->note - NOTE_MIN) % 12]));
- data += ('0' + (m->note - NOTE_MIN) / 12);
- } else
- {
- data += mpt::ToCharset(mpt::Charset::Locale, sndFile.GetNoteName(m->note));
- }
- } else
- {
- // No note
- data += " ";
- }
- // Instrument
- cursor.Move(0, 0, 1);
- if(selection.ContainsHorizontal(cursor))
- {
- if(m->instr)
- {
- data += ('0' + (m->instr / 10));
- data += ('0' + (m->instr % 10));
- } else
- {
- data += "..";
- }
- } else
- {
- data += " ";
- }
- // Volume
- cursor.Move(0, 0, 1);
- if(selection.ContainsHorizontal(cursor))
- {
- if(m->IsPcNote())
- {
- data += mpt::afmt::dec0<3>(m->GetValueVolCol());
- }
- else
- {
- if(m->volcmd != VOLCMD_NONE && m->vol <= 99)
- {
- data += sndFile.GetModSpecifications().GetVolEffectLetter(m->volcmd);
- data += mpt::afmt::dec0<2>(m->vol);
- } else
- {
- data += "...";
- }
- }
- } else
- {
- data += " ";
- }
-
- // Effect
- cursor.Move(0, 0, 1);
- if(selection.ContainsHorizontal(cursor))
- {
- if(m->IsPcNote())
- {
- data += mpt::afmt::dec0<3>(m->GetValueEffectCol());
- }
- else
- {
- if(m->command != CMD_NONE)
- {
- data += sndFile.GetModSpecifications().GetEffectLetter(m->command);
- } else
- {
- data += '.';
- }
- if(m->param != 0 && m->command != CMD_NONE)
- {
- data += mpt::afmt::HEX0<2>(m->param);
- } else
- {
- data += "..";
- }
- }
- } else
- {
- data += " ";
- }
- }
- // Next Row
- data += "\r\n";
- }
- return data;
- }
- // Try pasting a pattern selection from the system clipboard.
- bool PatternClipboard::Paste(CSoundFile &sndFile, PatternEditPos &pastePos, PasteModes mode, PatternRect &pasteRect, bool &orderChanged)
- {
- std::string data;
- if(!FromSystemClipboard(data) || !HandlePaste(sndFile, pastePos, mode, data, pasteRect, orderChanged))
- {
- // Fall back to internal clipboard if there's no valid pattern data in the system clipboard.
- return Paste(sndFile, pastePos, mode, pasteRect, instance.m_activeClipboard, orderChanged);
- }
- return true;
- }
- // Try pasting a pattern selection from an internal clipboard.
- bool PatternClipboard::Paste(CSoundFile &sndFile, PatternEditPos &pastePos, PasteModes mode, PatternRect &pasteRect, clipindex_t internalClipboard, bool &orderChanged)
- {
- if(internalClipboard >= instance.m_clipboards.size())
- return false;
-
- return HandlePaste(sndFile, pastePos, mode, instance.m_clipboards[internalClipboard].content, pasteRect, orderChanged);
- }
- // Paste from pattern or channel clipboard.
- bool PatternClipboard::Paste(CSoundFile &sndFile, PATTERNINDEX pattern, CHANNELINDEX channel)
- {
- PatternEditPos pastePos{0, ORDERINDEX_INVALID, pattern, channel != CHANNELINDEX_INVALID ? channel : CHANNELINDEX(0)};
- PatternRect pasteRect;
- bool orderChanged = false;
- return HandlePaste(sndFile, pastePos, pmOverwrite, (channel == CHANNELINDEX_INVALID ? instance.m_patternClipboard : instance.m_channelClipboard).content, pasteRect, orderChanged);
- }
- // Parse clipboard string and perform the pasting operation.
- bool PatternClipboard::HandlePaste(CSoundFile &sndFile, PatternEditPos &pastePos, PasteModes mode, const std::string &data, PatternRect &pasteRect, bool &orderChanged)
- {
- const std::string whitespace(" \n\r\t");
- PATTERNINDEX pattern = pastePos.pattern;
- ORDERINDEX &curOrder = pastePos.order;
- orderChanged = false;
- if(sndFile.GetpModDoc() == nullptr)
- return false;
- CModDoc &modDoc = *(sndFile.GetpModDoc());
- ModSequence &order = sndFile.Order();
- bool success = false;
- bool prepareUndo = true; // Prepare pattern for undo next time
- bool firstUndo = true; // For chaining undos (see overflow / multi-pattern paste)
- // Search for signature
- std::string::size_type pos, startPos = 0;
- MODTYPE pasteFormat = MOD_TYPE_NONE;
- while(pasteFormat == MOD_TYPE_NONE && (startPos = data.find("ModPlug Tracker ", startPos)) != std::string::npos)
- {
- startPos += 16;
- // Check paste format
- const std::string format = mpt::ToUpperCaseAscii(mpt::trim(data.substr(startPos, 3)));
- for(const auto &spec : ModSpecs::Collection)
- {
- if(format == GetFileExtension(spec->fileExtension, false))
- {
- pasteFormat = spec->internalType;
- startPos += 3;
- break;
- }
- }
- }
- // What is this I don't even
- if(startPos == std::string::npos)
- return false;
- // Skip whitespaces
- startPos = data.find_first_not_of(whitespace, startPos);
- if(startPos == std::string::npos)
- return false;
-
- // Multi-order stuff
- std::vector<PATTERNINDEX> patList;
- // Multi-order mix-paste stuff
- std::vector<ORDERINDEX> ordList;
- std::vector<std::string::size_type> patOffset;
- enum { kSinglePaste, kMultiInsert, kMultiOverwrite } patternMode = kSinglePaste;
- if(data.substr(startPos, 8) == "Orders: ")
- {
- // Pasting several patterns at once.
- patternMode = (mode == pmOverwrite) ? kMultiInsert : kMultiOverwrite;
- // Put new patterns after current pattern, if it exists
- if(order.IsValidPat(curOrder) && patternMode == kMultiInsert)
- curOrder++;
- pos = startPos + 8;
- startPos = data.find('\n', pos);
- ORDERINDEX writeOrder = curOrder;
- const bool onlyOrders = (startPos == std::string::npos);
- if(onlyOrders)
- {
- // Only create order list, no patterns
- startPos = data.size();
- } else
- {
- startPos++;
- }
- while(pos < startPos && pos != std::string::npos)
- {
- PATTERNINDEX insertPat;
- auto curPos = pos;
- // Next order item, please
- pos = data.find(',', pos + 1);
- if(pos != std::string::npos)
- pos++;
- if(data[curPos] == '+')
- {
- insertPat = order.GetIgnoreIndex();
- } else if(data[curPos] == '-')
- {
- insertPat = order.GetInvalidPatIndex();
- } else
- {
- insertPat = ConvertStrTo<PATTERNINDEX>(data.substr(curPos, 10));
- if(patternMode == kMultiOverwrite)
- {
- // We only want the order of pasted patterns now, do not create any new patterns
- ordList.push_back(insertPat);
- continue;
- }
- if(insertPat < patList.size() && patList[insertPat] != PATTERNINDEX_INVALID)
- {
- // Duplicate pattern
- insertPat = patList[insertPat];
- } else if(!onlyOrders)
- {
- // New pattern
- if(insertPat >= patList.size())
- {
- patList.resize(insertPat + 1, PATTERNINDEX_INVALID);
- }
- patList[insertPat] = modDoc.InsertPattern(64);
- insertPat = patList[insertPat];
- }
- }
- if((insertPat == order.GetIgnoreIndex() && !sndFile.GetModSpecifications().hasIgnoreIndex)
- || (insertPat == order.GetInvalidPatIndex() && !sndFile.GetModSpecifications().hasStopIndex)
- || insertPat == PATTERNINDEX_INVALID
- || patternMode == kMultiOverwrite)
- {
- continue;
- }
- if(order.insert(writeOrder, 1) == 0)
- {
- break;
- }
- order[writeOrder++] = insertPat;
- orderChanged = true;
- }
- if(patternMode == kMultiInsert)
- {
- if(!patList.empty())
- {
- // First pattern we're going to paste in.
- pattern = patList[0];
- }
- // We already modified the order list...
- success = true;
- pastePos.pattern = pattern;
- pastePos.row = 0;
- pastePos.channel = 0;
- } else
- {
- if(ordList.empty())
- return success;
- // Find pattern offsets
- pos = startPos;
- patOffset.reserve(ordList.size());
- bool patStart = false;
- while((pos = data.find_first_not_of(whitespace, pos)) != std::string::npos)
- {
- auto eol = data.find('\n', pos + 1);
- if(eol == std::string::npos)
- eol = data.size();
- if(data.substr(pos, 6) == "Rows: ")
- {
- patStart = true;
- } else if(data.substr(pos, 1) == "|" && patStart)
- {
- patOffset.push_back(pos);
- patStart = false;
- }
- pos = eol;
- }
- if(patOffset.empty())
- return success;
- startPos = patOffset[0];
- }
- }
- size_t curPattern = 0; // Currently pasted pattern for multi-paste
- ROWINDEX startRow = pastePos.row;
- ROWINDEX curRow = startRow;
- CHANNELINDEX startChan = pastePos.channel, col;
- // Can we actually paste at this position?
- if(!sndFile.Patterns.IsValidPat(pattern) || startRow >= sndFile.Patterns[pattern].GetNumRows() || startChan >= sndFile.GetNumChannels())
- {
- return success;
- }
- const CModSpecifications &sourceSpecs = CSoundFile::GetModSpecifications(pasteFormat);
- const bool overflowPaste = (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OVERFLOWPASTE) && mode != pmPasteFlood && mode != pmPushForward && patternMode != kMultiInsert && curOrder != ORDERINDEX_INVALID;
- const bool doITStyleMix = (mode == pmMixPasteIT);
- const bool doMixPaste = (mode == pmMixPaste) || doITStyleMix;
- const bool clipboardHasS3MCommands = (pasteFormat & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M));
- const bool insertNewPatterns = overflowPaste && (patternMode == kMultiOverwrite);
- PatternCursor startPoint(startRow, startChan, PatternCursor::lastColumn), endPoint(startRow, startChan, PatternCursor::firstColumn);
- ModCommand *patData = sndFile.Patterns[pattern].GetpModCommand(startRow, 0);
- auto multiPastePos = ordList.cbegin();
- pos = startPos;
- while(curRow < sndFile.Patterns[pattern].GetNumRows() || overflowPaste || patternMode == kMultiInsert)
- {
- // Parse next line
- pos = data.find_first_not_of(whitespace, pos);
- if(pos == std::string::npos)
- {
- // End of paste
- if(mode == pmPasteFlood && curRow != startRow && curRow < sndFile.Patterns[pattern].GetNumRows())
- {
- // Restarting pasting from beginning.
- pos = startPos;
- multiPastePos = ordList.cbegin();
- continue;
- } else
- {
- // Prevent infinite loop with malformed clipboard data.
- break;
- }
- }
- auto eol = data.find('\n', pos + 1);
- if(eol == std::string::npos)
- eol = data.size();
- // Handle multi-paste: Read pattern information
- if(patternMode != kSinglePaste)
- {
- // Parse pattern header lines
- bool parsedLine = true;
- if(data.substr(pos, 6) == "Rows: ")
- {
- pos += 6;
- // Advance to next pattern
- if(patternMode == kMultiOverwrite)
- {
- // In case of multi-pattern mix-paste, we know that we reached the end of the previous pattern and need to parse the next order now.
- multiPastePos++;
- if(multiPastePos == ordList.cend() || *multiPastePos >= patOffset.size())
- pos = data.size();
- else
- pos = patOffset[*multiPastePos];
- continue;
- }
- // Otherwise, parse this pattern header normally.
- do
- {
- if(curPattern >= patList.size())
- {
- return success;
- }
- pattern = patList[curPattern++];
- } while (pattern == PATTERNINDEX_INVALID);
- ROWINDEX numRows = ConvertStrTo<ROWINDEX>(data.substr(pos, 10));
- sndFile.Patterns[pattern].Resize(numRows);
- patData = sndFile.Patterns[pattern].GetpModCommand(0, 0);
- curRow = 0;
- prepareUndo = true;
- } else if(data.substr(pos, 6) == "Name: ")
- {
- pos += 6;
- auto name = mpt::trim_right(data.substr(pos, eol - pos - 1));
- sndFile.Patterns[pattern].SetName(name);
- } else if(data.substr(pos, 11) == "Signature: ")
- {
- pos += 11;
- auto pos2 = data.find("/", pos + 1);
- if(pos2 != std::string::npos)
- {
- pos2++;
- ROWINDEX rpb = ConvertStrTo<ROWINDEX>(data.substr(pos, pos2 - pos));
- ROWINDEX rpm = ConvertStrTo<ROWINDEX>(data.substr(pos2, eol - pos2));
- sndFile.Patterns[pattern].SetSignature(rpb, rpm);
- }
- } else if(data.substr(pos, 7) == "Swing: ")
- {
- pos += 7;
- TempoSwing swing;
- swing.resize(sndFile.Patterns[pattern].GetRowsPerBeat(), TempoSwing::Unity);
- size_t i = 0;
- while(pos != std::string::npos && pos < eol && i < swing.size())
- {
- swing[i++] = ConvertStrTo<TempoSwing::value_type>(data.substr(pos, eol - pos));
- pos = data.find(',', pos + 1);
- if(pos != std::string::npos)
- pos++;
- }
- sndFile.Patterns[pattern].SetTempoSwing(swing);
- } else
- {
- parsedLine = false;
- }
- if(parsedLine)
- {
- pos = eol;
- continue;
- }
- }
- if(data[pos] != '|')
- {
- // Not a valid line?
- pos = eol;
- continue;
- }
- if(overflowPaste)
- {
- // Handle overflow paste. Continue pasting in next pattern if enabled.
- // If Paste Flood is enabled, this won't be called due to obvious reasons.
- while(curRow >= sndFile.Patterns[pattern].GetNumRows())
- {
- curRow = 0;
- ORDERINDEX nextOrder = order.GetNextOrderIgnoringSkips(curOrder);
- if(nextOrder <= curOrder || !order.IsValidPat(nextOrder))
- {
- PATTERNINDEX newPat;
- if(!insertNewPatterns
- || curOrder >= sndFile.GetModSpecifications().ordersMax
- || (newPat = modDoc.InsertPattern(sndFile.Patterns[pattern].GetNumRows())) == PATTERNINDEX_INVALID
- || order.insert(curOrder + 1, 1, newPat) == 0)
- {
- return success;
- }
- orderChanged = true;
- nextOrder = curOrder + 1;
- }
- pattern = order[nextOrder];
- if(!sndFile.Patterns.IsValidPat(pattern)) return success;
- patData = sndFile.Patterns[pattern].GetpModCommand(0, 0);
- curOrder = nextOrder;
- prepareUndo = true;
- startRow = 0;
- }
- }
- success = true;
- col = startChan;
- // Paste columns
- while((pos + 11 < data.size()) && (data[pos] == '|'))
- {
- pos++;
- // Handle pasting large pattern into smaller pattern (e.g. 128-row pattern into MOD, which only allows 64 rows)
- ModCommand dummy;
- ModCommand &m = curRow < sndFile.Patterns[pattern].GetNumRows() ? patData[col] : dummy;
- // Check valid paste condition. Paste will be skipped if
- // - col is not a valid channelindex or
- // - doing mix paste and paste destination modcommand is a PCnote or
- // - doing mix paste and trying to paste PCnote on non-empty modcommand.
- const bool skipPaste =
- col >= sndFile.GetNumChannels() ||
- (doMixPaste && m.IsPcNote()) ||
- (doMixPaste && data[pos] == 'P' && !m.IsEmpty());
- if(skipPaste == false)
- {
- // Before changing anything in this pattern, we have to create an undo point.
- if(prepareUndo)
- {
- modDoc.GetPatternUndo().PrepareUndo(pattern, startChan, startRow, sndFile.GetNumChannels(), sndFile.Patterns[pattern].GetNumRows(), "Paste", !firstUndo);
- prepareUndo = false;
- firstUndo = false;
- }
- // ITSyle mixpaste requires that we keep a copy of the thing we are about to paste on
- // so that we can refer back to check if there was anything in e.g. the note column before we pasted.
- const ModCommand origModCmd = m;
- // push channel data below paste point first.
- if(mode == pmPushForward)
- {
- for(ROWINDEX pushRow = sndFile.Patterns[pattern].GetNumRows() - 1 - curRow; pushRow > 0; pushRow--)
- {
- patData[col + pushRow * sndFile.GetNumChannels()] = patData[col + (pushRow - 1) * sndFile.GetNumChannels()];
- }
- m.Clear();
- }
- PatternCursor::Columns firstCol = PatternCursor::lastColumn, lastCol = PatternCursor::firstColumn;
- // Note
- if(data[pos] != ' ' && (!doMixPaste || ((!doITStyleMix && origModCmd.note == NOTE_NONE) ||
- (doITStyleMix && origModCmd.note == NOTE_NONE && origModCmd.instr == 0 && origModCmd.volcmd == VOLCMD_NONE))))
- {
- firstCol = PatternCursor::noteColumn;
- m.note = NOTE_NONE;
- if(data[pos] == '=')
- m.note = NOTE_KEYOFF;
- else if(data[pos] == '^')
- m.note = NOTE_NOTECUT;
- else if(data[pos] == '~')
- m.note = NOTE_FADE;
- else if(data[pos] == 'P')
- {
- if(data[pos + 2] == 'S' || data[pos + 2] == 's')
- m.note = NOTE_PCS;
- else
- m.note = NOTE_PC;
- } else if (data[pos] != '.')
- {
- // Check note names
- for(uint8 i = 0; i < 12; i++)
- {
- if(data[pos] == NoteNamesSharp[i][0] && data[pos + 1] == NoteNamesSharp[i][1])
- {
- m.note = ModCommand::NOTE(i + NOTE_MIN);
- break;
- }
- }
- if(m.note != NOTE_NONE)
- {
- // Check octave
- m.note += (data[pos + 2] - '0') * 12;
- if(!m.IsNote())
- {
- // Invalid octave
- m.note = NOTE_NONE;
- }
- }
- }
- }
- // Instrument
- if(data[pos + 3] > ' ' && (!doMixPaste || ( (!doITStyleMix && origModCmd.instr == 0) ||
- (doITStyleMix && origModCmd.note == NOTE_NONE && origModCmd.instr == 0 && origModCmd.volcmd == VOLCMD_NONE) ) ))
- {
- firstCol = std::min(firstCol, PatternCursor::instrColumn);
- lastCol = std::max(lastCol, PatternCursor::instrColumn);
- if(data[pos + 3] >= '0' && data[pos + 3] <= ('0' + (MAX_INSTRUMENTS / 10)))
- {
- m.instr = (data[pos + 3] - '0') * 10 + (data[pos + 4] - '0');
- } else m.instr = 0;
- }
- // Volume
- if(data[pos + 5] > ' ' && (!doMixPaste || ((!doITStyleMix && origModCmd.volcmd == VOLCMD_NONE) ||
- (doITStyleMix && origModCmd.note == NOTE_NONE && origModCmd.instr == 0 && origModCmd.volcmd == VOLCMD_NONE))))
- {
- firstCol = std::min(firstCol, PatternCursor::volumeColumn);
- lastCol = std::max(lastCol, PatternCursor::volumeColumn);
- if(data[pos + 5] != '.')
- {
- if(m.IsPcNote())
- {
- m.SetValueVolCol(ConvertStrTo<uint16>(data.substr(pos + 5, 3)));
- } else
- {
- m.volcmd = VOLCMD_NONE;
- for(int i = VOLCMD_NONE + 1; i < MAX_VOLCMDS; i++)
- {
- const char cmd = sourceSpecs.GetVolEffectLetter(static_cast<VolumeCommand>(i));
- if(data[pos + 5] == cmd && cmd != '?')
- {
- m.volcmd = static_cast<VolumeCommand>(i);
- break;
- }
- }
- m.vol = (data[pos + 6] - '0') * 10 + (data[pos + 7] - '0');
- }
- } else
- {
- m.volcmd = VOLCMD_NONE;
- m.vol = 0;
- }
- }
- // Effect
- if(m.IsPcNote())
- {
- if(data[pos + 8] != '.' && data[pos + 8] > ' ')
- {
- firstCol = std::min(firstCol, PatternCursor::paramColumn);
- lastCol = std::max(lastCol, PatternCursor::paramColumn);
- m.SetValueEffectCol(ConvertStrTo<uint16>(data.substr(pos + 8, 3)));
- } else if(!origModCmd.IsPcNote())
- {
- // No value provided in clipboard
- if((m.command == CMD_MIDI || m.command == CMD_SMOOTHMIDI) && m.param < 128)
- m.SetValueEffectCol(static_cast<uint16>(Util::muldivr(m.param, ModCommand::maxColumnValue, 127)));
- else
- m.SetValueEffectCol(0);
- }
- } else
- {
- if(data[pos + 8] > ' ' && (!doMixPaste || ((!doITStyleMix && origModCmd.command == CMD_NONE) ||
- (doITStyleMix && origModCmd.command == CMD_NONE && origModCmd.param == 0))))
- {
- firstCol = std::min(firstCol, PatternCursor::effectColumn);
- lastCol = std::max(lastCol, PatternCursor::effectColumn);
- m.command = CMD_NONE;
- if(data[pos + 8] != '.')
- {
- for(int i = CMD_NONE + 1; i < MAX_EFFECTS; i++)
- {
- const char cmd = sourceSpecs.GetEffectLetter(static_cast<EffectCommand>(i));
- if(data[pos + 8] == cmd && cmd != '?')
- {
- m.command = static_cast<EffectCommand>(i);
- break;
- }
- }
- }
- }
- // Effect value
- if(data[pos + 9] > ' ' && (!doMixPaste || ((!doITStyleMix && (origModCmd.command == CMD_NONE || origModCmd.param == 0)) ||
- (doITStyleMix && origModCmd.command == CMD_NONE && origModCmd.param == 0))))
- {
- firstCol = std::min(firstCol, PatternCursor::paramColumn);
- lastCol = std::max(lastCol, PatternCursor::paramColumn);
- m.param = 0;
- if(data[pos + 9] != '.')
- {
- for(uint8 i = 0; i < 16; i++)
- {
- if(data[pos + 9] == szHexChar[i]) m.param |= (i << 4);
- if(data[pos + 10] == szHexChar[i]) m.param |= i;
- }
- }
- }
-
- // Speed / tempo command conversion
- if (sndFile.GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))
- {
- switch(m.command)
- {
- case CMD_SPEED:
- case CMD_TEMPO:
- if(!clipboardHasS3MCommands)
- {
- if(m.param < 32)
- m.command = CMD_SPEED;
- else
- m.command = CMD_TEMPO;
- } else
- {
- if(m.command == CMD_SPEED && m.param >= 32)
- m.param = CMD_TEMPO;
- else if(m.command == CMD_TEMPO && m.param < 32)
- m.param = CMD_SPEED;
- }
- break;
- }
- } else
- {
- switch(m.command)
- {
- case CMD_SPEED:
- case CMD_TEMPO:
- if(!clipboardHasS3MCommands)
- {
- if(m.param < 32)
- m.command = CMD_SPEED;
- else
- m.command = CMD_TEMPO;
- }
- break;
- }
- }
- }
- // Convert some commands, if necessary. With mix paste convert only
- // if the original modcommand was empty as otherwise the unchanged parts
- // of the old modcommand would falsely be interpreted being of type
- // origFormat and ConvertCommand could change them.
- if(pasteFormat != sndFile.GetType() && (!doMixPaste || origModCmd.IsEmpty()))
- m.Convert(pasteFormat, sndFile.GetType(), sndFile);
- // Sanitize PC events
- if(m.IsPcNote())
- {
- m.SetValueEffectCol(std::min(m.GetValueEffectCol(), static_cast<decltype(m.GetValueEffectCol())>(ModCommand::maxColumnValue)));
- m.SetValueVolCol(std::min(m.GetValueVolCol(), static_cast<decltype(m.GetValueEffectCol())>(ModCommand::maxColumnValue)));
- }
- // Adjust pattern selection
- if(col == startChan) startPoint.SetColumn(startChan, firstCol);
- if(endPoint.CompareColumn(PatternCursor(0, col, lastCol)) < 0) endPoint.SetColumn(col, lastCol);
- if(curRow > endPoint.GetRow()) endPoint.SetRow(curRow);
- pasteRect = PatternRect(startPoint, endPoint);
- }
- pos += 11;
- col++;
- }
- // Next row
- patData += sndFile.GetNumChannels();
- curRow++;
- pos = eol;
- }
- return success;
- }
- // Copy one of the internal clipboards to the system clipboard.
- bool PatternClipboard::SelectClipboard(clipindex_t which)
- {
- instance.m_activeClipboard = which;
- return ToSystemClipboard(instance.m_clipboards[instance.m_activeClipboard]);
- }
- // Switch to the next internal clipboard.
- bool PatternClipboard::CycleForward()
- {
- instance.m_activeClipboard++;
- if(instance.m_activeClipboard >= instance.m_clipboards.size())
- instance.m_activeClipboard = 0;
- return SelectClipboard(instance.m_activeClipboard);
- }
- // Switch to the previous internal clipboard.
- bool PatternClipboard::CycleBackward()
- {
- if(instance.m_activeClipboard == 0)
- instance.m_activeClipboard = instance.m_clipboards.size() - 1;
- else
- instance.m_activeClipboard--;
- return SelectClipboard(instance.m_activeClipboard);
- }
- // Set the maximum number of internal clipboards.
- void PatternClipboard::SetClipboardSize(clipindex_t maxEntries)
- {
- instance.m_clipboards.resize(maxEntries, {"", _T("unused")});
- LimitMax(instance.m_activeClipboard, maxEntries - 1);
- }
- // Check whether patterns can be pasted from clipboard
- bool PatternClipboard::CanPaste()
- {
- return !!IsClipboardFormatAvailable(CF_TEXT);
- }
- // System-specific clipboard functions
- bool PatternClipboard::ToSystemClipboard(const std::string_view &data)
- {
- Clipboard clipboard(CF_TEXT, data.size() + 1);
- if(auto dst = clipboard.As<char>())
- {
- std::copy(data.begin(), data.end(), dst);
- dst[data.size()] = '\0';
- return true;
- }
- return false;
- }
- // System-specific clipboard functions
- bool PatternClipboard::FromSystemClipboard(std::string &data)
- {
- Clipboard clipboard(CF_TEXT);
- if(auto cbdata = clipboard.Get(); cbdata.data())
- {
- if(cbdata.size() > 0)
- data.assign(mpt::byte_cast<char *>(cbdata.data()), cbdata.size() - 1);
- return !data.empty();
- }
- return false;
- }
- BEGIN_MESSAGE_MAP(PatternClipboardDialog, ResizableDialog)
- ON_EN_UPDATE(IDC_EDIT1, &PatternClipboardDialog::OnNumClipboardsChanged)
- ON_LBN_SELCHANGE(IDC_LIST1, &PatternClipboardDialog::OnSelectClipboard)
- ON_LBN_DBLCLK(IDC_LIST1, &PatternClipboardDialog::OnEditName)
- END_MESSAGE_MAP()
- PatternClipboardDialog PatternClipboardDialog::instance;
- void PatternClipboardDialog::DoDataExchange(CDataExchange *pDX)
- {
- DDX_Control(pDX, IDC_SPIN1, m_numClipboardsSpin);
- DDX_Control(pDX, IDC_LIST1, m_clipList);
- }
- PatternClipboardDialog::PatternClipboardDialog() : m_editNameBox(*this)
- {
- }
- void PatternClipboardDialog::Show()
- {
- instance.m_isLocked = true;
- if(!instance.m_isCreated)
- {
- instance.Create(IDD_CLIPBOARD, CMainFrame::GetMainFrame());
- instance.m_numClipboardsSpin.SetRange(0, int16_max);
- }
- instance.SetDlgItemInt(IDC_EDIT1, mpt::saturate_cast<UINT>(PatternClipboard::GetClipboardSize()), FALSE);
- instance.m_isLocked = false;
- instance.m_isCreated = true;
- instance.UpdateList();
-
- instance.SetWindowPos(nullptr, instance.m_posX, instance.m_posY, 0, 0, SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER | (instance.m_posX == -1 ? SWP_NOMOVE : 0));
- }
- void PatternClipboardDialog::OnNumClipboardsChanged()
- {
- if(m_isLocked)
- {
- return;
- }
- OnEndEdit();
- PatternClipboard::SetClipboardSize(GetDlgItemInt(IDC_EDIT1, nullptr, FALSE));
- UpdateList();
- }
- void PatternClipboardDialog::UpdateList()
- {
- if(instance.m_isLocked)
- {
- return;
- }
- instance.m_clipList.ResetContent();
- PatternClipboard::clipindex_t i = 0;
- for(const auto &clip : PatternClipboard::instance.m_clipboards)
- {
- const int item = instance.m_clipList.AddString(clip.description);
- instance.m_clipList.SetItemDataPtr(item, reinterpret_cast<void *>(i));
- if(PatternClipboard::instance.m_activeClipboard == i)
- {
- instance.m_clipList.SetCurSel(item);
- }
- i++;
- }
- }
- void PatternClipboardDialog::OnSelectClipboard()
- {
- if(m_isLocked)
- {
- return;
- }
- PatternClipboard::clipindex_t item = reinterpret_cast<PatternClipboard::clipindex_t>(m_clipList.GetItemDataPtr(m_clipList.GetCurSel()));
- PatternClipboard::SelectClipboard(item);
- OnEndEdit();
- }
- void PatternClipboardDialog::OnOK()
- {
- const CWnd *focus = GetFocus();
- if(focus == &m_editNameBox)
- {
- // User pressed enter in clipboard name edit box => cancel editing
- OnEndEdit();
- } else if(focus == &m_clipList)
- {
- // User pressed enter in the clipboard name list => start editing
- OnEditName();
- } else
- {
- ResizableDialog::OnOK();
- }
- }
- void PatternClipboardDialog::OnCancel()
- {
- if(GetFocus() == &m_editNameBox)
- {
- // User pressed enter in clipboard name edit box => just cancel editing
- m_editNameBox.DestroyWindow();
- return;
- }
- OnEndEdit(false);
- m_isCreated = false;
- m_isLocked = true;
- RECT rect;
- GetWindowRect(&rect);
- m_posX = rect.left;
- m_posY = rect.top;
- DestroyWindow();
- }
- void PatternClipboardDialog::OnEditName()
- {
- OnEndEdit();
- const int sel = m_clipList.GetCurSel();
- if(sel == LB_ERR)
- {
- return;
- }
- CRect rect;
- m_clipList.GetItemRect(sel, rect);
- rect.InflateRect(0, 2, 0, 2);
- // Create the edit control
- m_editNameBox.Create(WS_VISIBLE | WS_CHILD | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, rect, &m_clipList, 1);
- m_editNameBox.SetFont(m_clipList.GetFont());
- m_editNameBox.SetWindowText(PatternClipboard::instance.m_clipboards[sel].description);
- m_editNameBox.SetSel(0, -1, TRUE);
- m_editNameBox.SetFocus();
- SetWindowLongPtr(m_editNameBox.m_hWnd, GWLP_USERDATA, (LONG_PTR)m_clipList.GetItemDataPtr(sel));
- }
- void PatternClipboardDialog::OnEndEdit(bool apply)
- {
- if(m_editNameBox.GetSafeHwnd() == NULL)
- {
- return;
- }
- if(apply)
- {
- size_t sel = GetWindowLongPtr(m_editNameBox.m_hWnd, GWLP_USERDATA);
- if(sel >= PatternClipboard::instance.m_clipboards.size())
- {
- // What happened?
- return;
- }
- CString newName;
- m_editNameBox.GetWindowText(newName);
- PatternClipboard::instance.m_clipboards[sel].description = newName;
- }
- SetWindowLongPtr(m_editNameBox.m_hWnd, GWLP_USERDATA, LONG_PTR(-1));
- m_editNameBox.DestroyWindow();
- UpdateList();
- }
- BEGIN_MESSAGE_MAP(PatternClipboardDialog::CInlineEdit, CEdit)
- ON_WM_KILLFOCUS()
- END_MESSAGE_MAP()
- PatternClipboardDialog::CInlineEdit::CInlineEdit(PatternClipboardDialog &dlg) : parent(dlg)
- {
- }
- void PatternClipboardDialog::CInlineEdit::OnKillFocus(CWnd *newWnd)
- {
- parent.OnEndEdit(true);
- CEdit::OnKillFocus(newWnd);
- }
- OPENMPT_NAMESPACE_END
|