diff --git a/stuff/profiles/layouts/rooms/Default/menubar_template.xml b/stuff/profiles/layouts/rooms/Default/menubar_template.xml index d16dca2..b8f2486 100644 --- a/stuff/profiles/layouts/rooms/Default/menubar_template.xml +++ b/stuff/profiles/layouts/rooms/Default/menubar_template.xml @@ -54,6 +54,7 @@ MI_Paste MI_PasteAbove MI_PasteInto + MI_PasteDuplicate MI_Insert MI_InsertAbove MI_Clear diff --git a/toonz/sources/include/toonzqt/selectioncommandids.h b/toonz/sources/include/toonzqt/selectioncommandids.h index 1c7a69a..bba8673 100644 --- a/toonz/sources/include/toonzqt/selectioncommandids.h +++ b/toonz/sources/include/toonzqt/selectioncommandids.h @@ -10,6 +10,7 @@ #define MI_Paste "MI_Paste" #define MI_PasteAbove "MI_PasteAbove" #define MI_PasteInto "MI_PasteInto" +#define MI_PasteDuplicate "MI_PasteDuplicte" #define MI_PasteValues "MI_PasteValues" #define MI_PasteColors "MI_PasteColors" #define MI_PasteNames "MI_PasteNames" diff --git a/toonz/sources/toonz/celldata.h b/toonz/sources/toonz/celldata.h index e091e7f..4a40b5f 100644 --- a/toonz/sources/toonz/celldata.h +++ b/toonz/sources/toonz/celldata.h @@ -55,6 +55,9 @@ If skipEmptyCells == false do not skip setting empty cells in data*/ //! Return true if cell in TCellData can be set in \b xsh xsheet. bool canChange(TXsheet *xsh, int c0) const; + // This is used when pasting duplicate frames + void replaceCells(std::vector newCells) { m_cells = newCells; } + protected: bool canChange(TXshColumn *column, int index) const; void cloneZeraryFx(int index, std::vector &cells) const; diff --git a/toonz/sources/toonz/cellselection.cpp b/toonz/sources/toonz/cellselection.cpp index d41ec6b..f1040ab 100644 --- a/toonz/sources/toonz/cellselection.cpp +++ b/toonz/sources/toonz/cellselection.cpp @@ -1480,6 +1480,7 @@ void TCellSelection::enableCommands() { enableCommand(this, MI_CreateBlankDrawing, &TCellSelection::createBlankDrawings); enableCommand(this, MI_Duplicate, &TCellSelection::duplicateFrames); + enableCommand(this, MI_PasteDuplicate, &TCellSelection::pasteDuplicateCells); } //----------------------------------------------------------------------------- // Used in RenameCellField::eventFilter() @@ -1900,6 +1901,357 @@ void TCellSelection::pasteCells() { //----------------------------------------------------------------------------- +void TCellSelection::pasteDuplicateCells() { + 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(); + XsheetViewer *viewer = TApp::instance()->getCurrentXsheetViewer(); + + bool initUndo = false; + const TCellKeyframeData *cellKeyframeData = + dynamic_cast(mimeData); + + const TCellData *cellData = dynamic_cast(mimeData); + if (!cellData && cellKeyframeData) cellData = cellKeyframeData->getCellData(); + if (cellData) { + // go through all the reasons this might fail + if (isEmpty()) return; + + if (cellData->getCellCount() == 0) { + DVGui::error(QObject::tr("No data to paste.")); + return; + } + + // xsheet columns increase in number as they go to the right + // timeline "columns" (visually rows) increase in number as they go up + // To paste in the frames the user would expect, we need to adjust the + // "column" placement if using the timeline. + + if (viewer && !viewer->orientation()->isVerticalTimeline()) { + int cAdj = cellData->getColCount() - 1; + c0 -= cAdj; + c1 -= cAdj; + } + + // make sure we aren't trying to paste anything in locked columns + for (int i = 0; i < cellData->getColCount(); i++) { + TXshColumn *column = xsh->getColumn(c0 + i); + if (column && column->isLocked()) { + DVGui::warning(QObject::tr("Cannot paste cells on locked layers.")); + return; + } + } + + // make sure the camera column isn't included in the selection + if (c0 < 0) { + DVGui::warning(QObject::tr("Can't place drawings on the camera column.")); + return; + } + + int oldR0 = r0; + int oldC0 = c0; + int oldR1 = r1; + int oldC1 = c1; + if (!cellData->canChange(xsh, c0)) return; + + // find what cells we need + int cellCount = cellData->getCellCount(); + + // use a pair to ensure that only unique cells are duplicated + std::set cells; + std::vector cellsVector; + for (int i = 0; i < cellCount; i++) { + TXshCell cell = cellData->getCell(i); + cells.insert(cell); + cellsVector.push_back(cell); + } + + TXshSimpleLevel *sl; + TXshLevelP level; + std::set::iterator it = cells.begin(); + + // check that the selection only includes types that can be duplicated and + // edited + while (it != cells.end()) { + if (it->isEmpty()) { + it++; + continue; + } + level = it->m_level; + if (level) { + int levelType = level->getType(); + if (levelType == ZERARYFX_XSHLEVEL || levelType == PLT_XSHLEVEL || + levelType == SND_XSHLEVEL || levelType == SND_TXT_XSHLEVEL || + levelType == MESH_XSHLEVEL) { + DVGui::warning( + QObject::tr("Cannot duplicate a drawing in the current column")); + return; + } else if (level->getSimpleLevel() && + level->getSimpleLevel()->isReadOnly()) { + DVGui::warning( + QObject::tr("Cannot duplicate frames in read only levels")); + return; + } + } + it++; + } + + // now we do the work of duplicating cells + + // start a undo block first + initUndo = true; + TUndoManager::manager()->beginBlock(); + + // Turn on sync level strip changes with xsheet if it is off + bool syncXsheetOn = true; + if (!Preferences::instance()->isSyncLevelRenumberWithXsheetEnabled()) { + syncXsheetOn = false; + Preferences::instance()->setValue(syncLevelRenumberWithXsheet, true); + } + + // now actually duplicate + std::vector> cellPairs; + it = cells.begin(); + while (it != cells.end()) { + level = it->m_level; + if (level) { + sl = it->getSimpleLevel(); + if (sl) { + std::set frames; + TFrameId fid = it->getFrameId(); + frames.insert(fid); + FilmstripCmd::duplicate(sl, frames, true); + TXshCell newCell; + newCell.m_level = sl; + newCell.m_frameId = fid + 1; + cellPairs.push_back(std::make_pair(*it, newCell)); + } + } + it++; + } + + // turn off sync with xsheet if it wasn't on originally + if (syncXsheetOn == false) { + Preferences::instance()->setValue(syncLevelRenumberWithXsheet, false); + } + + // we should now have a vector with all original cells + // and a pair vector with the original cell and its duplicate + // let's make a new cellData populated with the new cells when needed. + std::vector newCells; + for (int i = 0; i < cellsVector.size(); i++) { + std::vector>::iterator pairIt = + cellPairs.begin(); + bool found = false; + while (pairIt != cellPairs.end()) { + if (cellsVector[i] == pairIt->first) { + found = true; + newCells.push_back(pairIt->second); + break; + } + pairIt++; + } + if (!found) { + newCells.push_back(cellsVector[i]); + } + } + + TCellData *newCellData = new TCellData(cellData); + newCellData->replaceCells(newCells); + + assert(newCellData->getCellCount() == cellData->getCellCount()); + + bool isPaste = pasteCellsWithoutUndo(newCellData, r0, c0, r1, c1); + + // 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); + + std::vector areColumnsEmpty; + int c; + for (c = c0; c <= c1; c++) { + TXshColumn *column = xsh->getColumn(c); + if (!column) { + areColumnsEmpty.push_back(false); + continue; + } + int newCr0, newCr1; + column->getRange(newCr0, newCr1); + areColumnsEmpty.push_back(!column || column->isEmpty() || + (newCr0 == r0 && newCr1 == r1)); + } + + if (!isPaste) { + TUndoManager::manager()->endBlock(); + if (cellPairs.size() > 0) { + TUndoManager::manager()->undo(); + TUndoManager::manager()->popUndo(1); + } + return; + } + + TUndoManager::manager()->add(new PasteCellsUndo( + r0, c0, r1, c1, oldR0, oldC0, oldR1, oldC1, areColumnsEmpty)); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + } + + const TKeyframeData *keyframeData = + dynamic_cast(mimeData); + if (!keyframeData && cellKeyframeData) + keyframeData = cellKeyframeData->getKeyframeData(); + if (keyframeData) { + if (keyframeData->m_keyData.empty()) { + DVGui::error(QObject::tr( + "It is not possible to paste data: there is nothing to paste.")); + return; + } + TKeyframeSelection selection; + if (isEmpty() && + TApp::instance()->getCurrentObject()->getObjectId() == + TStageObjectId::CameraId(xsh->getCameraColumnIndex())) + // Se la selezione e' vuota e l'objectId e' quello della camera sono nella + // colonna di camera quindi devo selezionare la row corrente e -1. + { + int row = TApp::instance()->getCurrentFrame()->getFrame(); + selection.select(row, -1); + } else { + // Retrieves all keyframe positions from mime data and translates them by + // (r0,c0) + std::set positions; + int newC0 = c0; + if (viewer && !viewer->orientation()->isVerticalTimeline() && !cellData) + newC0 = c0 - keyframeData->getColumnSpanCount() + 1; + TKeyframeSelection::Position offset(keyframeData->getKeyframesOffset()); + positions.insert(TKeyframeSelection::Position(r0 + offset.first, + newC0 + offset.second)); + keyframeData->getKeyframes(positions); + selection.select(positions); + + if (!cellKeyframeData) { + // Retrieve the keyframes bbox + r1 = positions.rbegin()->first; + c1 = c0; + + std::set::const_iterator it, + end = positions.end(); + for (it = positions.begin(); it != end; ++it) + c1 = std::max(c1, it->second); + } + } + if (!initUndo) { + initUndo = true; + TUndoManager::manager()->beginBlock(); + } + selection.pasteKeyframesWithShift(r0, r1, c0, c1); + } + + // if (const DrawingData* drawingData = + // dynamic_cast(mimeData)) { + // if (isEmpty()) // Se la selezione delle celle e' vuota ritorno. + // return; + + // set frameIds; + // drawingData->getFrames(frameIds); + // TXshSimpleLevel* level = drawingData->getLevel(); + // if (level && !frameIds.empty()) + // pasteDrawingsInCellWithoutUndo(xsh, level, frameIds, r0, c0); + // if (!initUndo) { + // initUndo = true; + // TUndoManager::manager()->beginBlock(); + // } + // TUndoManager::manager()->add( + // new PasteDrawingsInCellUndo(level, frameIds, r0, c0)); + //} + // if (const StrokesData* strokesData = + // dynamic_cast(mimeData)) { + // if (isEmpty()) // Se la selezione delle celle e' vuota ritorno. + // return; + + // TImageP img = xsh->getCell(r0, c0).getImage(false); + // if (!img && r0 > 0) { + // TXshCell cell = xsh->getCell(r0 - 1, c0); + // TXshLevel* xl = cell.m_level.getPointer(); + // if (xl && (xl->getType() != OVL_XSHLEVEL || + // xl->getPath().getFrame() != TFrameId::NO_FRAME)) + // img = cell.getImage(false); + // } + // if (!initUndo) { + // initUndo = true; + // TUndoManager::manager()->beginBlock(); + // } + // RasterImageData* rasterImageData = 0; + // if (TToonzImageP ti = img) { + // rasterImageData = strokesData->toToonzImageData(ti); + // pasteRasterImageInCell(r0, c0, rasterImageData); + // } + // else if (TRasterImageP ri = img) { + // double dpix, dpiy; + // ri->getDpi(dpix, dpiy); + // if (dpix == 0 || dpiy == 0) { + // TPointD dpi = xsh->getScene()->getCurrentCamera()->getDpi(); + // dpix = dpi.x; + // dpiy = dpi.y; + // ri->setDpi(dpix, dpiy); + // } + // rasterImageData = strokesData->toFullColorImageData(ri); + // pasteRasterImageInCell(r0, c0, rasterImageData); + // } + // else + // pasteStrokesInCell(r0, c0, strokesData); + //} + // if (const RasterImageData* rasterImageData = + // dynamic_cast(mimeData)) { + // if (isEmpty()) // Se la selezione delle celle e' vuota ritorno. + // return; + + // TImageP img = xsh->getCell(r0, c0).getImage(false); + // if (!img && r0 > 0) { + // TXshCell cell = xsh->getCell(r0 - 1, c0); + // TXshLevel* xl = cell.m_level.getPointer(); + // if (xl && (xl->getType() != OVL_XSHLEVEL || + // xl->getPath().getFrame() != TFrameId::NO_FRAME)) + // img = cell.getImage(false); + // } + // const FullColorImageData* fullColData = + // dynamic_cast(rasterImageData); + // TToonzImageP ti(img); + // TVectorImageP vi(img); + // if (!initUndo) { + // initUndo = true; + // TUndoManager::manager()->beginBlock(); + // } + // if (fullColData && (vi || ti)) { + // DVGui::error(QObject::tr( + // "The copied selection cannot be pasted in the current + // drawing.")); + // return; + // } + // if (vi) { + // TXshSimpleLevel* sl = xsh->getCell(r0, c0).getSimpleLevel(); + // if (!sl) sl = xsh->getCell(r0 - 1, c0).getSimpleLevel(); + // assert(sl); + // StrokesData* strokesData = + // rasterImageData->toStrokesData(sl->getScene()); + // pasteStrokesInCell(r0, c0, strokesData); + // } + // else + // pasteRasterImageInCell(r0, c0, rasterImageData); + //} + if (!initUndo) { + DVGui::error(QObject::tr("There are no copied cells to duplicate.")); + return; + } + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TUndoManager::manager()->endBlock(); +} + +//----------------------------------------------------------------------------- + void TCellSelection::deleteCells() { if (isEmpty()) return; int r0, c0, r1, c1; diff --git a/toonz/sources/toonz/cellselection.h b/toonz/sources/toonz/cellselection.h index cf002db..99842ab 100644 --- a/toonz/sources/toonz/cellselection.h +++ b/toonz/sources/toonz/cellselection.h @@ -45,6 +45,7 @@ public: void copyCells(); void pasteCells(); + void pasteDuplicateCells(); void deleteCells(); void cutCells(); void cutCells(bool withoutCopy); diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index ebfdc26..2fe7012 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1763,6 +1763,7 @@ void MainWindow::defineActions() { // createMenuEditAction(MI_PasteNew, tr("&Paste New"), ""); createMenuCellsAction(MI_MergeFrames, tr("&Merge"), ""); createMenuEditAction(MI_PasteInto, tr("&Paste Into"), ""); + createMenuEditAction(MI_PasteDuplicate, tr("&Paste as a Copy"), ""); createRightClickMenuAction(MI_PasteValues, tr("&Paste Color && Name"), ""); createRightClickMenuAction(MI_PasteColors, tr("Paste Color"), ""); createRightClickMenuAction(MI_PasteNames, tr("Paste Name"), ""); @@ -2074,7 +2075,7 @@ void MainWindow::defineActions() { // createRightClickMenuAction(MI_OpenCurrentScene, tr("&Current Scene"), // ""); - //createMenuWindowsAction(MI_OpenExport, tr("&Export"), ""); + // createMenuWindowsAction(MI_OpenExport, tr("&Export"), ""); createMenuWindowsAction(MI_OpenFileBrowser, tr("&File Browser"), ""); createMenuWindowsAction(MI_OpenFileViewer, tr("&Flipbook"), ""); diff --git a/toonz/sources/toonz/menubar.cpp b/toonz/sources/toonz/menubar.cpp index 0770c0c..c402483 100644 --- a/toonz/sources/toonz/menubar.cpp +++ b/toonz/sources/toonz/menubar.cpp @@ -1135,6 +1135,7 @@ QMenuBar *StackedMenuBar::createFullMenuBar() { addMenuItem(editMenu, MI_PasteAbove); // addMenuItem(editMenu, MI_PasteNew); addMenuItem(editMenu, MI_PasteInto); + addMenuItem(editMenu, MI_PasteDuplicate); addMenuItem(editMenu, MI_Insert); addMenuItem(editMenu, MI_InsertAbove); addMenuItem(editMenu, MI_Clear); @@ -1421,8 +1422,8 @@ QMenuBar *StackedMenuBar::createFullMenuBar() { #endif addMenuItem(windowsMenu, MI_StartupPopup); addMenuItem(windowsMenu, MI_OpenGuidedDrawingControls); - //windowsMenu->addSeparator(); - //addMenuItem(windowsMenu, MI_OpenExport); + // windowsMenu->addSeparator(); + // addMenuItem(windowsMenu, MI_OpenExport); windowsMenu->addSeparator(); addMenuItem(windowsMenu, MI_MaximizePanel); addMenuItem(windowsMenu, MI_FullScreenWindow); diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index abf2595..d857745 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -3344,6 +3344,9 @@ void CellArea::createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell) { menu.addAction(cmdManager->getAction(MI_Cut)); menu.addAction(cmdManager->getAction(MI_Copy)); menu.addAction(cmdManager->getAction(MI_Paste)); + if (!soundTextCellsSelected) { + menu.addAction(cmdManager->getAction(MI_PasteDuplicate)); + } QMenu *pasteSpecialMenu = new QMenu(tr("Paste Special"), this); {