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);
{