aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp1227
1 files changed, 1227 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp
new file mode 100644
index 00000000..930786ea
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/mptrack/PatternClipboard.cpp
@@ -0,0 +1,1227 @@
+/*
+ * 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