diff --git a/toonz/sources/include/toonz/txshlevelcolumn.h b/toonz/sources/include/toonz/txshlevelcolumn.h index a351505..e519264 100644 --- a/toonz/sources/include/toonz/txshlevelcolumn.h +++ b/toonz/sources/include/toonz/txshlevelcolumn.h @@ -85,6 +85,9 @@ Return \b TFx. */ TFx *getFx() const override; + // Used in TCellData::getNumbers + bool setNumbers(int row, int rowCount, const TXshCell cells[]); + private: // not implemented TXshLevelColumn(const TXshLevelColumn &); diff --git a/toonz/sources/include/toonzqt/selectioncommandids.h b/toonz/sources/include/toonzqt/selectioncommandids.h index 99ebe5e..50fa74b 100644 --- a/toonz/sources/include/toonzqt/selectioncommandids.h +++ b/toonz/sources/include/toonzqt/selectioncommandids.h @@ -36,5 +36,6 @@ #define MI_Collapse "MI_Collapse" #define MI_ExplodeChild "MI_ExplodeChild" #define MI_ToggleEditInPlace "MI_ToggleEditInPlace" +#define MI_PasteNumbers "MI_PasteNumbers" #endif diff --git a/toonz/sources/toonz/celldata.cpp b/toonz/sources/toonz/celldata.cpp index 2bef123..709f2fd 100644 --- a/toonz/sources/toonz/celldata.cpp +++ b/toonz/sources/toonz/celldata.cpp @@ -10,6 +10,7 @@ #include "toonz/txshzeraryfxlevel.h" #include "toonz/tcolumnfx.h" #include "toonz/fxdag.h" +#include "toonz/txshlevelcolumn.h" //----------------------------------------------------------------------------- @@ -99,6 +100,42 @@ bool TCellData::getCells(TXsheet *xsh, int r0, int c0, int &r1, int &c1, } //----------------------------------------------------------------------------- +// Paste only cell numbers. +// As a special behavior, enable to copy one column and paste into +// multiple columns. + +bool TCellData::getNumbers(TXsheet *xsh, int r0, int c0, int &r1, + int &c1) const { + r1 = r0 + m_rowCount - 1; + bool oneToMulti = m_colCount == 1 && c0 < c1; + if (!oneToMulti) c1 = c0 + m_colCount - 1; + + bool cellSet = false; + + for (int c = c0; c <= c1; c++) { + TXshColumn *column = xsh->getColumn(c); + if (!column || column->isEmpty()) continue; + TXshLevelColumn *levelColumn = column->getLevelColumn(); + if (!levelColumn) continue; + + std::vector cells = m_cells; + + int sourceColIndex = (oneToMulti) ? 0 : c - c0; + int sourceCellIndex = sourceColIndex * m_rowCount; + + if (!canChange(column, sourceColIndex)) continue; + + bool isSet = levelColumn->setNumbers(r0, m_rowCount, + &cells[sourceColIndex * m_rowCount]); + + cellSet = cellSet || isSet; + } + xsh->updateFrameCount(); + + return cellSet; +} + +//----------------------------------------------------------------------------- /*-- c0 を起点に、TCellDataの選択範囲のカラム幅分、 全てのカラムがcanChangeかどうか調べる。 --*/ diff --git a/toonz/sources/toonz/celldata.h b/toonz/sources/toonz/celldata.h index 0556a0e..e091e7f 100644 --- a/toonz/sources/toonz/celldata.h +++ b/toonz/sources/toonz/celldata.h @@ -47,6 +47,11 @@ If skipEmptyCells == false do not skip setting empty cells in data*/ bool insert = true, bool doZeraryClone = true, bool skipEmptyCells = true) const; + // Paste only cell numbers. + // As a special behavior, enable to copy one column and paste into + // multiple columns. + bool getNumbers(TXsheet *xsh, int r0, int c0, int &r1, int &c1) const; + //! Return true if cell in TCellData can be set in \b xsh xsheet. bool canChange(TXsheet *xsh, int c0) const; diff --git a/toonz/sources/toonz/cellselection.cpp b/toonz/sources/toonz/cellselection.cpp index 3913628..ef7676d 100644 --- a/toonz/sources/toonz/cellselection.cpp +++ b/toonz/sources/toonz/cellselection.cpp @@ -1033,6 +1033,118 @@ public: int getHistoryType() override { return HistoryType::Xsheet; } }; +//============================================================================= + +bool pasteNumbersWithoutUndo(int &r0, int &c0, int &r1, int &c1) { + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + const TCellData *cellData = dynamic_cast(mimeData); + + if (!cellData) return false; + + if (r0 < 0 || c0 < 0) return false; + + bool ret = cellData->getNumbers(xsh, r0, c0, r1, c1); + if (!ret) return false; + + // Se la selezione corrente e' TCellSelection selezione le celle copiate + TCellSelection *cellSelection = dynamic_cast( + TApp::instance()->getCurrentSelection()->getSelection()); + if (cellSelection) cellSelection->selectCells(r0, c0, r1, c1); + return true; +} + +//============================================================================= + +class OverwritePasteNumbersUndo final : public TUndo { + TCellSelection *m_oldSelection; + TCellSelection *m_newSelection; + QMimeData *m_data; + QMimeData *m_beforeData; + +public: + OverwritePasteNumbersUndo(int r0, int c0, int r1, int c1, int oldR0, + int oldC0, int oldR1, int oldC1, + TCellData *beforeData) { + QClipboard *clipboard = QApplication::clipboard(); + // keep the pasted data + TCellData *data = new TCellData(); + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + data->setCells(xsh, r0, c0, r1, c1); + m_data = data->clone(); + m_newSelection = new TCellSelection(); + m_newSelection->selectCells(r0, c0, r1, c1); + m_oldSelection = new TCellSelection(); + m_oldSelection->selectCells(oldR0, oldC0, oldR1, oldC1); + // keep the cells before pasting + m_beforeData = beforeData->clone(); + } + + ~OverwritePasteNumbersUndo() { + delete m_newSelection; + delete m_oldSelection; + delete m_data; + delete m_beforeData; + } + + void undo() const override { + int r0, c0, r1, c1; + m_newSelection->getSelectedCells(r0, c0, r1, c1); + + int oldR0, oldC0, oldR1, oldC1; + m_oldSelection->getSelectedCells(oldR0, oldC0, oldR1, oldC1); + + QClipboard *clipboard = QApplication::clipboard(); + int c0BeforeCut = c0; + int c1BeforeCut = c1; + cutCellsWithoutUndo(r0, c0, r1, c1); + + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + + // keep the clipboard content + QMimeData *mimeData = cloneData(clipboard->mimeData()); + + // execute pasting + clipboard->setMimeData(cloneData(m_beforeData), QClipboard::Clipboard); + pasteCellsWithoutUndo(r0, c0, r1, c1, true, false, false); + + // restore clipboard + clipboard->setMimeData(mimeData, QClipboard::Clipboard); + + // revert old cell selection + TCellSelection *cellSelection = dynamic_cast( + TApp::instance()->getCurrentSelection()->getSelection()); + if (cellSelection && oldR0 != -1 && oldC0 != -1) + cellSelection->selectCells(oldR0, oldC0, oldR1, oldC1); + + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + } + + void redo() const override { + int r0, c0, r1, c1; + m_newSelection->getSelectedCells(r0, c0, c1, r1); + QClipboard *clipboard = QApplication::clipboard(); + + // keep the clipboard content + QMimeData *mimeData = cloneData(clipboard->mimeData()); + + clipboard->setMimeData(cloneData(m_data), QClipboard::Clipboard); + // Cut delle celle che sono in newSelection + pasteCellsWithoutUndo(r0, c0, c1, r1, false, false); + + // restore clipboard + clipboard->setMimeData(mimeData, QClipboard::Clipboard); + + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + } + + int getSize() const override { return sizeof(*this); } + + QString getHistoryString() override { return QObject::tr("Paste Numbers"); } + int getHistoryType() override { return HistoryType::Xsheet; } +}; + //----------------------------------------------------------------------------- // if at least one of the cell in the range, return false bool checkIfCellsHaveTheSameContent(const int &r0, const int &c0, const int &r1, @@ -1206,6 +1318,8 @@ void TCellSelection::enableCommands() { enableCommand(this, MI_Reframe4, &TCellSelection::reframe4Cells); enableCommand(this, MI_ReframeWithEmptyInbetweens, &TCellSelection::reframeWithEmptyInbetweens); + + enableCommand(this, MI_PasteNumbers, &TCellSelection::overwritePasteNumbers); } //----------------------------------------------------------------------------- // Used in RenameCellField::eventFilter() @@ -1244,7 +1358,8 @@ bool TCellSelection::isEnabledCommand( MI_Reframe4, MI_ReframeWithEmptyInbetweens, MI_Undo, - MI_Redo}; + MI_Redo, + MI_PasteNumbers}; return commands.contains(commandId); } @@ -2095,6 +2210,75 @@ void TCellSelection::overWritePasteCells() { } //----------------------------------------------------------------------------- +// special paste command - overwrite paste cell numbers only + +void TCellSelection::overwritePasteNumbers() { + int r0, c0, r1, c1; + getSelectedCells(r0, c0, r1, c1); + + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + if (const TCellData *cellData = dynamic_cast(mimeData)) { + if (isEmpty()) return; + + if (cellData->getCellCount() == 0) { + DVGui::error(QObject::tr("No data to paste.")); + return; + } + + int oldR0 = r0; + int oldC0 = c0; + int oldR1 = r1; + int oldC1 = c1; + + // Check if the copied cells are the same type as the target columns + if (!cellData->canChange(xsh, c0)) { + DVGui::error( + QObject::tr("It is not possible to paste the cells: " + "Some column is locked or column type is not match.")); + return; + } + + // Check Circular References + int i; + for (i = 0; i < cellData->getCellCount(); i++) { + if (!xsh->checkCircularReferences(cellData->getCell(i))) continue; + DVGui::error( + QObject::tr("It is not possible to paste the cells: there is a " + "circular reference.")); + return; + } + + // store celldata for undo + r1 = r0 + cellData->getRowCount() - 1; + if (cellData->getColCount() != 1 || c0 == c1) + c1 = c0 + cellData->getColCount() - 1; + TCellData *beforeData = new TCellData(); + beforeData->setCells(xsh, r0, c0, r1, c1); + + bool isPaste = pasteNumbersWithoutUndo(r0, c0, r1, c1); + + if (!isPaste) { + delete beforeData; + return; + } + + // r0,c0,r1,c1 : pasted region oldR0,oldC0,oldR1,oldC1 : selected area + // before pasting + TUndoManager::manager()->add(new OverwritePasteNumbersUndo( + r0, c0, r1, c1, oldR0, oldC0, oldR1, oldC1, beforeData)); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + + delete beforeData; + } else + DVGui::error(QObject::tr("Cannot paste data \n Nothing to paste")); + + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); +} + +//----------------------------------------------------------------------------- // called from RenameCellField::RenameCell void TCellSelection::renameCells(TXshCell &cell) { diff --git a/toonz/sources/toonz/cellselection.h b/toonz/sources/toonz/cellselection.h index 10bffb1..1e26cfa 100644 --- a/toonz/sources/toonz/cellselection.h +++ b/toonz/sources/toonz/cellselection.h @@ -50,6 +50,8 @@ public: /*- セルの上書きペースト -*/ void overWritePasteCells(); + // paste cell numbers only + void overwritePasteNumbers(); //! \note: puo' anche essere r0>r1 o c0>c1 void selectCells(int r0, int c0, int r1, int c1); diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index e9b13f7..b10db67 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1871,6 +1871,7 @@ void MainWindow::defineActions() { ""); createRightClickMenuAction(MI_SetKeyframes, tr("&Set Key"), "Z"); + createRightClickMenuAction(MI_PasteNumbers, tr("&Paste Numbers"), ""); createToggle(MI_ViewCamera, tr("&Camera Box"), "", ViewCameraToggleAction ? 1 : 0, MenuViewCommandType); diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index 67b179a..74e80b3 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -2629,9 +2629,7 @@ void CellArea::createCellMenu(QMenu &menu, bool isCellSelected) { menu.addAction(cmdManager->getAction(MI_Autorenumber)); } menu.addAction(cmdManager->getAction(MI_ReplaceLevel)); - menu.addAction(cmdManager->getAction(MI_ReplaceParentDirectory)); - { // replace with another level in scene cast std::vector levels; @@ -2693,6 +2691,7 @@ void CellArea::createCellMenu(QMenu &menu, bool isCellSelected) { menu.addSeparator(); if (!soundCellsSelected) menu.addAction(cmdManager->getAction(MI_ImportMagpieFile)); + menu.addAction(cmdManager->getAction(MI_PasteNumbers)); } //----------------------------------------------------------------------------- /*! replace level with another level in the cast diff --git a/toonz/sources/toonzlib/txshlevelcolumn.cpp b/toonz/sources/toonzlib/txshlevelcolumn.cpp index a6aeec2..47c012d 100644 --- a/toonz/sources/toonzlib/txshlevelcolumn.cpp +++ b/toonz/sources/toonzlib/txshlevelcolumn.cpp @@ -209,5 +209,103 @@ void TXshLevelColumn::saveData(TOStream &os) { } //----------------------------------------------------------------------------- +// Used in TCellData::getNumbers +bool TXshLevelColumn::setNumbers(int row, int rowCount, + const TXshCell cells[]) { + if (m_cells.empty()) return false; + // Check availability. + // - if source cells are all empty, do nothing + // - also, if source or target cells contain NO_FRAME, do nothing + bool isSrcAllEmpty = true; + for (int i = 0; i < rowCount; i++) { + // checking target cells + int currentTgtIndex = row + i - m_first; + if (currentTgtIndex < m_cells.size()) { + TXshCell tgtCell = m_cells[currentTgtIndex]; + if (!tgtCell.isEmpty() && tgtCell.m_frameId == TFrameId::NO_FRAME) + return false; + } + // checking source cells + TXshCell srcCell = cells[i]; + if (!srcCell.isEmpty()) { + if (srcCell.m_frameId == TFrameId::NO_FRAME) return false; + isSrcAllEmpty = false; + } + } + if (isSrcAllEmpty) return false; + + // Find a level to input. + // If the first target cell is empty, search the upper cells, and lower cells + // and use a level of firsty-found ocupied neighbor cell. + TXshLevelP currentLevel; + int tmpIndex = std::min(row - m_first, (int)m_cells.size() - 1); + // search upper cells + while (tmpIndex >= 0) { + TXshCell tmpCell = m_cells[tmpIndex]; + if (!tmpCell.isEmpty() && tmpCell.m_frameId != TFrameId::NO_FRAME) { + currentLevel = tmpCell.m_level; + break; + } + tmpIndex--; + } + // if not found any level in upper cells, then search the lower cells + if (!currentLevel) { + tmpIndex = std::max(row - m_first, 0); + while (tmpIndex < (int)m_cells.size()) { + TXshCell tmpCell = m_cells[tmpIndex]; + if (!tmpCell.isEmpty() && tmpCell.m_frameId != TFrameId::NO_FRAME) { + currentLevel = tmpCell.m_level; + break; + } + tmpIndex++; + } + // in the case any level for input could not be found + if (!currentLevel) return false; + } + + // Resize the cell container + int ra = row; + int rb = row + rowCount - 1; + int c_rb = m_first + m_cells.size() - 1; + if (row > c_rb) { + int newCellCount = row - m_first + rowCount; + m_cells.resize(newCellCount); + } else if (row < m_first) { + int delta = m_first - row; + m_cells.insert(m_cells.begin(), delta, TXshCell()); + m_first = row; + } + if (rb > c_rb) { + for (int i = 0; i < rb - c_rb; ++i) m_cells.push_back(TXshCell()); + } + + // Paste numbers. + for (int i = 0; i < rowCount; i++) { + int dstIndex = row - m_first + i; + TXshCell dstCell = m_cells[dstIndex]; + TXshCell srcCell = cells[i]; + if (srcCell.isEmpty()) { + m_cells[dstIndex] = TXshCell(); + } else { + if (!dstCell.isEmpty()) currentLevel = dstCell.m_level; + m_cells[dstIndex] = TXshCell(currentLevel, srcCell.m_frameId); + } + } + + // Update the cell container. + while (!m_cells.empty() && m_cells.back().isEmpty()) { + m_cells.pop_back(); + } + while (!m_cells.empty() && m_cells.front().isEmpty()) { + m_cells.erase(m_cells.begin()); + m_first++; + } + if (m_cells.empty()) { + m_first = 0; + } + return true; +} + +//----------------------------------------------------------------------------- PERSIST_IDENTIFIER(TXshLevelColumn, "levelColumn")