diff --git a/toonz/sources/include/toonz/txsheet.h b/toonz/sources/include/toonz/txsheet.h index 9edc3220..adf1f269 100644 --- a/toonz/sources/include/toonz/txsheet.h +++ b/toonz/sources/include/toonz/txsheet.h @@ -588,6 +588,9 @@ in TXsheetImp. void notifyStageObjectAdded(const TStageObjectId id); bool isReferenceManagementIgnored(TDoubleParam *); + void convertToImplicitHolds(); + void convertToExplicitHolds(); + protected: bool checkCircularReferences(TXsheet *childCandidate); diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 8e796237..098a7067 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -2021,6 +2021,10 @@ void MainWindow::defineActions() { createMenuXsheetAction(MI_RemoveEmptyColumns, QT_TR_NOOP("Remove Empty Columns"), "", "remove_empty_columns"); + createMenuXsheetAction(MI_ConvertToImplicitHolds, + QT_TR_NOOP("Convert to use Implicit Holds"), "", ""); + createMenuXsheetAction(MI_ConvertToExplicitHolds, + QT_TR_NOOP("Convert to use Explicit Holds"), "", ""); createMenuXsheetAction(MI_LipSyncPopup, QT_TR_NOOP("&Apply Lip Sync to Column"), "Alt+L", "dialogue"); diff --git a/toonz/sources/toonz/menubar.cpp b/toonz/sources/toonz/menubar.cpp index d01a3d2b..a799495f 100644 --- a/toonz/sources/toonz/menubar.cpp +++ b/toonz/sources/toonz/menubar.cpp @@ -433,6 +433,9 @@ void TopBar::loadMenubar() { addMenuItem(sceneMenu, MI_LipSyncPopup); sceneMenu->addSeparator(); addMenuItem(sceneMenu, MI_RemoveEmptyColumns); + sceneMenu->addSeparator(); + addMenuItem(sceneMenu, MI_ConvertToImplicitHolds); + addMenuItem(sceneMenu, MI_ConvertToExplicitHolds); // Menu' LEVEL QMenu *levelMenu = addMenu(ShortcutTree::tr("Level"), m_menuBar); diff --git a/toonz/sources/toonz/menubarcommandids.h b/toonz/sources/toonz/menubarcommandids.h index cbfdd12f..803baabd 100644 --- a/toonz/sources/toonz/menubarcommandids.h +++ b/toonz/sources/toonz/menubarcommandids.h @@ -44,6 +44,8 @@ #define MI_ImportMagpieFile "MI_ImportMagpieFile" #define MI_NewNoteLevel "MI_NewNoteLevel" #define MI_RemoveEmptyColumns "MI_RemoveEmptyColumns" +#define MI_ConvertToImplicitHolds "MI_ConvertToImplicitHolds" +#define MI_ConvertToExplicitHolds "MI_ConvertToExplicitHolds" #define MI_NewProject "MI_NewProject" #define MI_OpenRecentProject "MI_OpenRecentProject" #define MI_LoadProject "MI_LoadProject" diff --git a/toonz/sources/toonz/xsheetcmd.cpp b/toonz/sources/toonz/xsheetcmd.cpp index 96dfee12..7731ad91 100644 --- a/toonz/sources/toonz/xsheetcmd.cpp +++ b/toonz/sources/toonz/xsheetcmd.cpp @@ -1158,6 +1158,53 @@ public: //============================================================ +static void convertHoldType(int holdType) { + TTool::Application *app = TTool::getApplication(); + TXsheet *xsh = app->getCurrentScene()->getScene()->getXsheet(); + + if (!xsh) return; + + int answer = DVGui::MsgBox( + QString(QObject::tr("Converting scene to use %1 Holds can only be undone " + "using 'Revert Scene'. Save before converting.\nDo " + "you want to continue?") + .arg(holdType == 0 ? QObject::tr("Implicit") + : QObject::tr("Explicit"))), + QObject::tr("Continue"), QObject::tr("Cancel"), 1); + + if (answer == 0 || answer == 2) return; + + QAction *action = + CommandManager::instance()->getAction(MI_ToggleImplicitHold); + if (holdType == 0) { + xsh->convertToImplicitHolds(); + if (action && !action->isChecked()) action->trigger(); + } else { + xsh->convertToExplicitHolds(); + if (action && action->isChecked()) action->trigger(); + } + + app->getCurrentScene()->setDirtyFlag(); + + app->getCurrentXsheet()->notifyXsheetChanged(); +} + +class ConvertToImplicitHoldsCommand final : public MenuItemHandler { +public: + ConvertToImplicitHoldsCommand() + : MenuItemHandler(MI_ConvertToImplicitHolds) {} + void execute() override { XshCmd::convertHoldType(0); } +} ConvertToImplicitHoldsCommand; + +class ConvertToExplicitHoldsCommand final : public MenuItemHandler { +public: + ConvertToExplicitHoldsCommand() + : MenuItemHandler(MI_ConvertToExplicitHolds) {} + void execute() override { XshCmd::convertHoldType(1); } +} ConvertToExplicitHoldsCommand; + +//============================================================ + } // namespace XshCmd //***************************************************************************** diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index 38c43b87..9040cfad 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -1965,3 +1965,119 @@ ExpressionReferenceMonitor *TXsheet::getExpRefMonitor() const { return m_imp->m_expRefMonitor; } //--------------------------------------------------------- + +void TXsheet::convertToImplicitHolds() { + int cols = getColumnCount(); + if (!cols) return; + + set visitedXshs; + visitedXshs.insert(this); + + for (int c = 0; c < cols; c++) { + TXshColumn *column = getColumn(c); + if (!column || column->isEmpty()) continue; + + TXshCellColumn *cc = column->getCellColumn(); + if (!cc || cc->getColumnType() == TXshColumn::ColumnType::eSoundTextType || + cc->getColumnType() == TXshColumn::ColumnType::eSoundType) + continue; + + int r0, r1; + if (!cc->getRange(r0, r1)) continue; + + bool stopFrameSet = false; + TXshCell prevCell; + for (int r = r0; r <= r1; r++) { + TXshCell cell = cc->getCell(r); + + if (cell.isEmpty()) { + if (!stopFrameSet) { + // Set a stop frame if one hasn't been set yet + prevCell = TXshCell(prevCell.m_level, TFrameId::STOP_FRAME); + cc->setCell(r, prevCell); + stopFrameSet = true; + } + } else { + TXshLevel *level = cell.m_level.getPointer(); + if (level && level->getChildLevel()) { + TXsheet *childXsh = level->getChildLevel()->getXsheet(); + if (visitedXshs.count(childXsh) == 0) { + visitedXshs.insert(childXsh); + childXsh->convertToImplicitHolds(); + } + } + + // Keep 1st instance of new cells, replace duplicates with an empty + // frame + if (cell != prevCell) + prevCell = cell; + else if (cc->getColumnType() == TXshColumn::ColumnType::eZeraryFxType) { + std::vector cells; + cells.push_back(TXshCell(0, TFrameId::EMPTY_FRAME)); + cc->setCells(r, 1, &cells[0]); + } else + cc->setCell(r, TXshCell(0, TFrameId::EMPTY_FRAME)); + + stopFrameSet = false; + } + } + + // Add a final stop frame + cc->setCell(r1 + 1, TXshCell(prevCell.m_level, TFrameId::STOP_FRAME)); + } +} + +//--------------------------------------------------------- + +void TXsheet::convertToExplicitHolds() { + int cols = getColumnCount(); + if (!cols) return; + + set visitedXshs; + visitedXshs.insert(this); + + for (int c = 0; c < cols; c++) { + TXshColumn *column = getColumn(c); + if (!column || column->isEmpty()) continue; + + TXshCellColumn *cc = column->getCellColumn(); + if (!cc || cc->getColumnType() == TXshColumn::ColumnType::eSoundTextType || + cc->getColumnType() == TXshColumn::ColumnType::eSoundType) + continue; + + int r0, r1; + if (!cc->getRange(r0, r1)) continue; + + int frameCount = getFrameCount() - 1; + bool stopFrameSet = false; + TXshCell prevCell; + + r1 = std::max(r1, frameCount); + + for (int r = r0; r <= r1; r++) { + TXshCell cell = cc->getCell(r); + + TXshLevel *level = cell.m_level.getPointer(); + if (level && level->getChildLevel()) { + TXsheet *childXsh = level->getChildLevel()->getXsheet(); + if (visitedXshs.count(childXsh) == 0) { + visitedXshs.insert(childXsh); + childXsh->convertToImplicitHolds(); + } + } + + if (cell != prevCell && !cell.isEmpty()) prevCell = cell; + + if (cell.getFrameId().isStopFrame()) { + prevCell = TXshCell(0, TFrameId::EMPTY_FRAME); + if (cc->getColumnType() == TXshColumn::ColumnType::eZeraryFxType) { + std::vector cells; + cells.push_back(prevCell); + cc->setCells(r, 1, &cells[0]); + } else + cc->setCell(r, TXshCell(prevCell)); + } else + cc->setCell(r, prevCell); + } + } +}