From 2a922ad7ffac18b432de532714fe9d2227c52ad2 Mon Sep 17 00:00:00 2001 From: Thomas Knoefel Date: Fri, 22 Dec 2023 23:17:04 +0100 Subject: [PATCH] added CSV Sort functionality --- src/MultiReplacePanel.cpp | 177 ++++++++++++++++++++++++++++++++++-- src/MultiReplacePanel.h | 13 ++- src/StaticDialog/resource.h | 21 +++-- 3 files changed, 191 insertions(+), 20 deletions(-) diff --git a/src/MultiReplacePanel.cpp b/src/MultiReplacePanel.cpp index 9836db0..cbd0bd4 100644 --- a/src/MultiReplacePanel.cpp +++ b/src/MultiReplacePanel.cpp @@ -124,11 +124,10 @@ void MultiReplace::positionAndResizeControls(int windowWidth, int windowHeight) ctrlMap[IDC_QUOTECHAR_STATIC] = { 586, 215, 40, 25, WC_STATIC, L"Quote:", SS_RIGHT, NULL }; ctrlMap[IDC_QUOTECHAR_EDIT] = { 628, 215, 15, 20, WC_EDIT, NULL, ES_LEFT | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL , L"Quote: ', \", or empty" }; - //ctrlMap[IDC_COLUMN_DROP_BUTTON] = { 480, 183, 50, 25, WC_BUTTON, L"Drop", BS_PUSHBUTTON | WS_TABSTOP, NULL }; - //ctrlMap[IDC_COLUMN_COPY_BUTTON] = { 538, 183, 50, 25, WC_BUTTON, L"Copy", BS_PUSHBUTTON | WS_TABSTOP, NULL }; - ctrlMap[IDC_COLUMN_SORT_BUTTON] = { 480, 183, 25, 25, WC_BUTTON, L"\u25B2\u25BC", BS_PUSHBUTTON | WS_TABSTOP, L"Sort Columns" }; - ctrlMap[IDC_COLUMN_DROP_BUTTON] = { 518, 183, 25, 25, WC_BUTTON, L"\u2716", BS_PUSHBUTTON | WS_TABSTOP, L"Drop Columns" }; - ctrlMap[IDC_COLUMN_COPY_BUTTON] = { 556, 183, 25, 25, WC_BUTTON, L"\U0001F5CD", BS_PUSHBUTTON | WS_TABSTOP, L"Copy Columns to Clipboard" }; + ctrlMap[IDC_COLUMN_SORT_DESC_BUTTON] = { 476, 183, 17, 25, WC_BUTTON, L"\u25B2", BS_PUSHBUTTON | WS_TABSTOP, L"Sort Descending" }; + ctrlMap[IDC_COLUMN_SORT_ASC_BUTTON] = { 494, 183, 17, 25, WC_BUTTON, L"\u25BC", BS_PUSHBUTTON | WS_TABSTOP, L"Sort Ascending" }; + ctrlMap[IDC_COLUMN_DROP_BUTTON] = { 522, 183, 25, 25, WC_BUTTON, L"\u2716", BS_PUSHBUTTON | WS_TABSTOP, L"Drop Columns" }; + ctrlMap[IDC_COLUMN_COPY_BUTTON] = { 560, 183, 25, 25, WC_BUTTON, L"\U0001F5CD", BS_PUSHBUTTON | WS_TABSTOP, L"Copy Columns to Clipboard" }; ctrlMap[IDC_COLUMN_HIGHLIGHT_BUTTON] = { 596, 183, 50, 25, WC_BUTTON, L"Show", BS_PUSHBUTTON | WS_TABSTOP, L"Column highlight: On/Off" }; ctrlMap[IDC_STATUS_MESSAGE] = { 14, 260, 600, 24, WC_STATIC, L"", WS_VISIBLE | SS_LEFT, NULL }; @@ -1231,6 +1230,24 @@ INT_PTR CALLBACK MultiReplace::run_dlgProc(UINT message, WPARAM wParam, LPARAM l } break; + case IDC_COLUMN_SORT_ASC_BUTTON: + { + handleDelimiterPositions(DelimiterOperation::LoadAll); + if (columnDelimiterData.isValid()) { + handleSortColumns(SortDirection::Ascending); + } + } + break; + + case IDC_COLUMN_SORT_DESC_BUTTON: + { + handleDelimiterPositions(DelimiterOperation::LoadAll); + if (columnDelimiterData.isValid()) { + handleSortColumns(SortDirection::Descending); + } + } + break; + case IDC_COLUMN_DROP_BUTTON: { handleDelimiterPositions(DelimiterOperation::LoadAll); @@ -2811,6 +2828,138 @@ void MultiReplace::copyTextToClipboard(const std::wstring& text, int textCount) #pragma region Scope +void MultiReplace::handleSortColumns(SortDirection sortDirection) +{ + // Validate column delimiter data + if (!columnDelimiterData.isValid()) { + showStatusMessage(L"Invalid column or delimiter data.", RGB(255, 0, 0)); + return; + } + SendMessage(_hScintilla, SCI_BEGINUNDOACTION, 0, 0); + + std::vector combinedData; + std::vector index; + size_t lineCount = lineDelimiterPositions.size(); + combinedData.reserve(lineCount); // Optimizing memory allocation + index.reserve(lineCount); + + // Iterate through each line to extract content of specified columns + for (size_t i = 1; i < lineCount; ++i) { + const auto& lineInfo = lineDelimiterPositions[i]; + CombinedColumns rowData; + rowData.columns.resize(columnDelimiterData.inputColumns.size()); + + size_t columnIndex = 0; + for (int columnNumber : columnDelimiterData.inputColumns) { + LRESULT startPos, endPos; + + // Calculate start and end positions for each column + if (columnNumber == 1) { + startPos = lineInfo.startPosition; + } + else if (columnNumber - 2 < lineInfo.positions.size()) { + startPos = lineInfo.positions[columnNumber - 2].position + columnDelimiterData.delimiterLength; + } + else { + continue; + } + + if (columnNumber - 1 < lineInfo.positions.size()) { + endPos = lineInfo.positions[columnNumber - 1].position; + } + else { + endPos = lineInfo.endPosition; + } + + // Buffer to hold the text + std::vector buffer(static_cast(endPos - startPos) + 1); + + // Prepare TextRange structure for Scintilla + Sci_TextRange tr; + tr.chrg.cpMin = startPos; + tr.chrg.cpMax = endPos; + tr.lpstrText = buffer.data(); + + // Extract text for the column + send(SCI_GETTEXTRANGE, 0, reinterpret_cast(&tr), true); + rowData.columns[columnIndex++] = std::string(buffer.data()); + } + + combinedData.push_back(rowData); + index.push_back(i); + } + + // Sorting logic + std::sort(index.begin(), index.end(), [&](const size_t& a, const size_t& b) { + size_t adjustedA = a - 1; + size_t adjustedB = b - 1; + + for (size_t i = 0; i < columnDelimiterData.inputColumns.size(); ++i) { + if (combinedData[adjustedA].columns[i] != combinedData[adjustedB].columns[i]) { + return sortDirection == SortDirection::Ascending ? + combinedData[adjustedA].columns[i] < combinedData[adjustedB].columns[i] : + combinedData[adjustedA].columns[i] > combinedData[adjustedB].columns[i]; + } + } + return false; + }); + + // Reordering lines in Scintilla based on the sorted index + reorderLinesInScintilla(index); + + SendMessage(_hScintilla, SCI_ENDUNDOACTION, 0, 0); +} + +void MultiReplace::reorderLinesInScintilla(const std::vector& sortedIndex) +{ + // Step 1: Extract and save the header line + std::string headerLine; + LRESULT headerStart = SendMessage(_hScintilla, SCI_POSITIONFROMLINE, 0, 0); + LRESULT headerEnd = SendMessage(_hScintilla, SCI_GETLINEENDPOSITION, 0, 0); + std::vector headerBuffer(static_cast(headerEnd - headerStart) + 1); + Sci_TextRange headerTr; + headerTr.chrg.cpMin = headerStart; + headerTr.chrg.cpMax = headerEnd; + headerTr.lpstrText = headerBuffer.data(); + SendMessage(_hScintilla, SCI_GETTEXTRANGE, 0, reinterpret_cast(&headerTr)); + headerLine = std::string(headerBuffer.data()); + if (!headerLine.empty() && headerLine.back() != '\n') { + headerLine += "\n"; // Ensure it ends with a newline + } + + // Step 2: Extract the text of each line based on the sorted index + std::vector lines; + lines.reserve(sortedIndex.size()); + for (size_t i = 0; i < sortedIndex.size(); ++i) { + size_t idx = sortedIndex[i]; // The actual line number in the document + LRESULT lineStart = SendMessage(_hScintilla, SCI_POSITIONFROMLINE, idx, 0); + LRESULT lineEnd = SendMessage(_hScintilla, SCI_GETLINEENDPOSITION, idx, 0); + + std::vector buffer(static_cast(lineEnd - lineStart) + 1); + Sci_TextRange tr; + tr.chrg.cpMin = lineStart; + tr.chrg.cpMax = lineEnd; + tr.lpstrText = buffer.data(); + + send(SCI_GETTEXTRANGE, 0, reinterpret_cast(&tr), true); + lines.push_back(std::string(buffer.data())); + if (i < sortedIndex.size() - 1) { + lines.back().append("\n"); + } + } + + // Step 3: Clear all content from Scintilla + SendMessage(_hScintilla, SCI_CLEARALL, 0, 0); + + // Step 4: Re-insert the header line first + SendMessage(_hScintilla, SCI_APPENDTEXT, headerLine.length(), reinterpret_cast(headerLine.c_str())); + + // Step 5: Re-insert the sorted lines + for (const auto& line : lines) { + SendMessage(_hScintilla, SCI_APPENDTEXT, line.length(), reinterpret_cast(line.c_str())); + } +} + void MultiReplace::handleCopyColumnsToClipboard() { if (!columnDelimiterData.isValid()) { @@ -3001,6 +3150,7 @@ bool MultiReplace::parseColumnAndDelimiterData() { return false; } + std::vector inputColumns; std::set columns; std::wstring::size_type start = 0; std::wstring::size_type end = columnDataString.find(',', start); @@ -3023,7 +3173,9 @@ bool MultiReplace::parseColumnAndDelimiterData() { } for (int i = startRange; i <= endRange; ++i) { - columns.insert(i); + if (columns.insert(i).second) { + inputColumns.push_back(i); + } } } catch (const std::exception&) { @@ -3043,7 +3195,9 @@ bool MultiReplace::parseColumnAndDelimiterData() { return false; } - columns.insert(column); + if (columns.insert(column).second) { + inputColumns.push_back(column); + } } catch (const std::exception&) { showStatusMessage(L"Syntax error in column data", RGB(255, 0, 0)); @@ -3091,8 +3245,10 @@ bool MultiReplace::parseColumnAndDelimiterData() { columnDelimiterData.columns.clear(); return false; } - - columns.insert(column); + auto insertResult = columns.insert(column); + if (insertResult.second) { // Check if the insertion was successful + inputColumns.push_back(column); // Add to the inputColumns vector + } } catch (const std::exception&) { showStatusMessage(L"Syntax error in column data", RGB(255, 0, 0)); @@ -3118,6 +3274,7 @@ bool MultiReplace::parseColumnAndDelimiterData() { columnDelimiterData.columnChanged = !(columnDelimiterData.columns == columns); // Set columnDelimiterData values + columnDelimiterData.inputColumns = inputColumns; columnDelimiterData.columns = columns; columnDelimiterData.extendedDelimiter = tempExtendedDelimiter; columnDelimiterData.delimiterLength = tempExtendedDelimiter.length(); @@ -4734,6 +4891,8 @@ void MultiReplace::loadSettingsFromIni(const std::wstring& iniFilePath) { EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_NUM_EDIT), columnModeSelected); EnableWindow(GetDlgItem(_hSelf, IDC_DELIMITER_EDIT), columnModeSelected); EnableWindow(GetDlgItem(_hSelf, IDC_QUOTECHAR_EDIT), columnModeSelected); + EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_SORT_DESC_BUTTON), columnModeSelected); + EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_SORT_ASC_BUTTON), columnModeSelected); EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_DROP_BUTTON), columnModeSelected); EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_COPY_BUTTON), columnModeSelected); EnableWindow(GetDlgItem(_hSelf, IDC_COLUMN_HIGHLIGHT_BUTTON), columnModeSelected); diff --git a/src/MultiReplacePanel.h b/src/MultiReplacePanel.h index 2f9a0ca..91ad34d 100644 --- a/src/MultiReplacePanel.h +++ b/src/MultiReplacePanel.h @@ -90,6 +90,7 @@ struct SelectionRange { }; struct ColumnDelimiterData { + std::vector inputColumns; // original order of the columns std::set columns; std::string extendedDelimiter; std::string quoteChar; @@ -109,6 +110,10 @@ struct DelimiterPosition { LRESULT position; }; +struct CombinedColumns { + std::vector columns; +}; + struct LineInfo { std::vector positions; LRESULT startPosition = 0; @@ -121,6 +126,10 @@ struct ColumnInfo { SIZE_T startColumnIndex; }; +enum class SortDirection { + Ascending, + Descending +}; // Lua Engine struct LuaVariables { @@ -301,7 +310,7 @@ class MultiReplace : public StaticDialog IDC_FIND_BUTTON, IDC_FIND_NEXT_BUTTON, IDC_FIND_PREV_BUTTON, IDC_REPLACE_BUTTON }; const std::vector columnRadioDependentElements = { - IDC_COLUMN_NUM_EDIT, IDC_DELIMITER_EDIT, IDC_QUOTECHAR_EDIT, IDC_COLUMN_DROP_BUTTON, IDC_COLUMN_COPY_BUTTON, IDC_COLUMN_HIGHLIGHT_BUTTON + IDC_COLUMN_NUM_EDIT, IDC_DELIMITER_EDIT, IDC_QUOTECHAR_EDIT, IDC_COLUMN_SORT_DESC_BUTTON, IDC_COLUMN_SORT_ASC_BUTTON, IDC_COLUMN_DROP_BUTTON, IDC_COLUMN_COPY_BUTTON, IDC_COLUMN_HIGHLIGHT_BUTTON }; //Initialization @@ -363,6 +372,8 @@ class MultiReplace : public StaticDialog void copyTextToClipboard(const std::wstring& text, int textCount); //Scope + void handleSortColumns(SortDirection sortDirection); + void reorderLinesInScintilla(const std::vector& sortedIndex); void handleCopyColumnsToClipboard(); void handleDeleteColumns(); bool parseColumnAndDelimiterData(); diff --git a/src/StaticDialog/resource.h b/src/StaticDialog/resource.h index 6d8540c..535c8e7 100644 --- a/src/StaticDialog/resource.h +++ b/src/StaticDialog/resource.h @@ -73,16 +73,17 @@ #define IDC_ALL_TEXT_RADIO 5452 #define IDC_SELECTION_RADIO 5453 #define IDC_COLUMN_MODE_RADIO 5454 -#define IDC_COLUMN_SORT_BUTTON 5455 -#define IDC_COLUMN_DROP_BUTTON 5456 -#define IDC_COLUMN_COPY_BUTTON 5457 -#define IDC_COLUMN_HIGHLIGHT_BUTTON 5458 -#define IDC_COLUMN_NUM_EDIT 5459 -#define IDC_DELIMITER_EDIT 5460 -#define IDC_QUOTECHAR_EDIT 5461 -#define IDC_DELIMITER_STATIC 5462 -#define IDC_COLUMN_NUM_STATIC 5463 -#define IDC_QUOTECHAR_STATIC 5464 +#define IDC_COLUMN_SORT_DESC_BUTTON 5455 +#define IDC_COLUMN_SORT_ASC_BUTTON 5456 +#define IDC_COLUMN_DROP_BUTTON 5457 +#define IDC_COLUMN_COPY_BUTTON 5458 +#define IDC_COLUMN_HIGHLIGHT_BUTTON 5459 +#define IDC_COLUMN_NUM_EDIT 5460 +#define IDC_DELIMITER_EDIT 5461 +#define IDC_QUOTECHAR_EDIT 5462 +#define IDC_DELIMITER_STATIC 5463 +#define IDC_COLUMN_NUM_STATIC 5464 +#define IDC_QUOTECHAR_STATIC 5465 #define IDC_STATIC_FRAME 5501 #define IDC_REPLACE_LIST 5503