diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h index 9f0fab1..39939b2 100644 --- a/toonz/sources/include/toonz/preferences.h +++ b/toonz/sources/include/toonz/preferences.h @@ -270,6 +270,9 @@ public: int matchLevelFormat(const TFilePath &fp) const; //!< Returns the \a nonnegative index of the first level format //! matching the specified file path, or \p -1 if none. + bool isAutoRemoveUnusedLevelsEnabled() const { + return isAutoExposeEnabled() && getBoolValue(autoRemoveUnusedLevels); + } // Saving tab TPixel getRasterBackgroundColor() const { diff --git a/toonz/sources/include/toonz/preferencesitemids.h b/toonz/sources/include/toonz/preferencesitemids.h index d826461..735d752 100644 --- a/toonz/sources/include/toonz/preferencesitemids.h +++ b/toonz/sources/include/toonz/preferencesitemids.h @@ -56,6 +56,7 @@ enum PreferencesItemId { initialLoadTlvCachingBehavior, columnIconLoadingPolicy, levelFormats, // need to be handle separately + autoRemoveUnusedLevels, //---------- // Saving diff --git a/toonz/sources/include/toonz/toonzscene.h b/toonz/sources/include/toonz/toonzscene.h index 3c0c97b..99673e7 100644 --- a/toonz/sources/include/toonz/toonzscene.h +++ b/toonz/sources/include/toonz/toonzscene.h @@ -112,9 +112,9 @@ public: /*! \return The \a coded path to be used for import. */ - TFilePath getImportedLevelPath(const TFilePath path) - const; //!< Builds the path to be used during a level import - //!< operation. + TFilePath getImportedLevelPath( + const TFilePath path) const; //!< Builds the path to be used during a + //!< level import operation. /*! \details If convertion is required, a new level file will be created and \p levelPath will be substituted with its new path. @@ -276,6 +276,7 @@ private: ToonzScene(const ToonzScene &); ToonzScene &operator=(const ToonzScene &); +public: // if the option is set in the preferences, // remove the scene numbers("c####_") from the file name std::wstring getLevelNameWithoutSceneNumber(std::wstring orgName); diff --git a/toonz/sources/toonz/iocommand.cpp b/toonz/sources/toonz/iocommand.cpp index 40d72ff..d20d94d 100644 --- a/toonz/sources/toonz/iocommand.cpp +++ b/toonz/sources/toonz/iocommand.cpp @@ -21,6 +21,7 @@ #include "cachefxcommand.h" #include "xdtsio.h" #include "expressionreferencemanager.h" +#include "levelcommand.h" // TnzTools includes #include "tools/toolhandle.h" @@ -356,6 +357,37 @@ int getLevelType(const TFilePath &actualPath) { } //=========================================================================== +// removeSameNamedUnusedLevel(scene, actualPath, levelName); +//--------------------------------------------------------------------------- + +void removeSameNamedUnusedLevel(ToonzScene *scene, const TFilePath actualPath, + std::wstring levelName) { + if (QString::fromStdWString(levelName).isEmpty()) { + // if the option is set in the preferences, + // remove the scene numbers("c####_") from the file name + levelName = scene->getLevelNameWithoutSceneNumber(actualPath.getWideName()); + } + + TLevelSet *levelSet = scene->getLevelSet(); + NameModifier nm(levelName); + levelName = nm.getNext(); + while (1) { + TXshLevel *existingLevel = levelSet->getLevel(levelName); + // if the level name is not used in the cast, nothing to do + if (!existingLevel) return; + // try if the existing level is unused in the xsheet and remove from the + // cast + else if (LevelCmd::removeLevelFromCast(existingLevel, scene, false)) { + DVGui::info(QObject::tr("Removed unused level %1 from the scene cast. " + "(This behavior can be disabled in Preferences.)") + .arg(QString::fromStdWString(levelName))); + return; + } + levelName = nm.getNext(); + } +} + +//=========================================================================== // class LoadLevelUndo //--------------------------------------------------------------------------- @@ -865,6 +897,11 @@ TXshLevel *loadLevel(ToonzScene *scene, bool isFirstTime = !xl; std::wstring name = actualPath.getWideName(); + if (isFirstTime && expose && + Preferences::instance()->isAutoRemoveUnusedLevelsEnabled()) { + removeSameNamedUnusedLevel(scene, actualPath, levelName); + } + IoCmd::ConvertingPopup *convertingPopup = new IoCmd::ConvertingPopup( TApp::instance()->getMainWindow(), QString::fromStdWString(name) + @@ -1394,6 +1431,15 @@ bool IoCmd::saveScene(const TFilePath &path, int flags) { TXsheet *xsheet = 0; if (saveSubxsheet) xsheet = TApp::instance()->getCurrentXsheet()->getXsheet(); + // Automatically remove unused levels + if (!saveSubxsheet && + Preferences::instance()->isAutoRemoveUnusedLevelsEnabled()) { + if (LevelCmd::removeUnusedLevelsFromCast(false)) + DVGui::info( + QObject::tr("Removed unused levels from the scene cast. (This " + "behavior can be disabled in Preferences.)")); + } + // If the scene will be saved in the different folder, check out the scene // cast. // if the cast contains the level specified with $scenefolder alias, diff --git a/toonz/sources/toonz/levelcommand.cpp b/toonz/sources/toonz/levelcommand.cpp index 0e764cb..bf38066 100644 --- a/toonz/sources/toonz/levelcommand.cpp +++ b/toonz/sources/toonz/levelcommand.cpp @@ -62,46 +62,70 @@ public: } // namespace -//============================================================================= -// RemoveUnusedLevelCommand //----------------------------------------------------------------------------- -class RemoveUnusedLevelsCommand final : public MenuItemHandler { -public: - RemoveUnusedLevelsCommand() : MenuItemHandler(MI_RemoveUnused) {} +bool LevelCmd::removeUnusedLevelsFromCast(bool showMessage) { + TApp *app = TApp::instance(); + ToonzScene *scene = app->getCurrentScene()->getScene(); - void execute() override { - TApp *app = TApp::instance(); - ToonzScene *scene = app->getCurrentScene()->getScene(); + TLevelSet *levelSet = scene->getLevelSet(); - TLevelSet *levelSet = scene->getLevelSet(); + std::set usedLevels; + scene->getTopXsheet()->getUsedLevels(usedLevels); - std::set usedLevels; - scene->getTopXsheet()->getUsedLevels(usedLevels); + std::vector unused; - std::vector unused; - - for (int i = 0; i < levelSet->getLevelCount(); i++) { - TXshLevel *xl = levelSet->getLevel(i); - if (usedLevels.count(xl) == 0) unused.push_back(xl); + for (int i = 0; i < levelSet->getLevelCount(); i++) { + TXshLevel *xl = levelSet->getLevel(i); + if (usedLevels.count(xl) == 0) unused.push_back(xl); + } + if (unused.empty()) { + if (showMessage) DVGui::error(QObject::tr("No unused levels")); + return false; + } else { + TUndoManager *um = TUndoManager::manager(); + um->beginBlock(); + for (int i = 0; i < (int)unused.size(); i++) { + TXshLevel *xl = unused[i]; + um->add(new DeleteLevelUndo(xl)); + scene->getLevelSet()->removeLevel(xl); } - if (unused.empty()) { - DVGui::error(QObject::tr("No unused levels")); - return; - } else { - TUndoManager *um = TUndoManager::manager(); - um->beginBlock(); - for (int i = 0; i < (int)unused.size(); i++) { - TXshLevel *xl = unused[i]; - um->add(new DeleteLevelUndo(xl)); - scene->getLevelSet()->removeLevel(xl); - } - TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); - TApp::instance()->getCurrentScene()->notifyCastChange(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + TApp::instance()->getCurrentScene()->notifyCastChange(); + + um->endBlock(); + } + return true; +} - um->endBlock(); +bool LevelCmd::removeLevelFromCast(TXshLevel *level, ToonzScene *scene, + bool showMessage) { + if (!scene) scene = TApp::instance()->getCurrentScene()->getScene(); + if (scene->getChildStack()->getTopXsheet()->isLevelUsed(level)) { + if (showMessage) { + DVGui::error( + QObject::tr("It is not possible to delete the used level %1.") + .arg(QString::fromStdWString( + level->getName()))); //"E_CantDeleteUsedLevel_%1" } + return false; + } else { + TUndoManager *um = TUndoManager::manager(); + um->add(new DeleteLevelUndo(level)); + scene->getLevelSet()->removeLevel(level); } + return true; +} + +//============================================================================= +// RemoveUnusedLevelCommand +//----------------------------------------------------------------------------- + +class RemoveUnusedLevelsCommand final : public MenuItemHandler { +public: + RemoveUnusedLevelsCommand() : MenuItemHandler(MI_RemoveUnused) {} + + void execute() override { LevelCmd::removeUnusedLevelsFromCast(); } } removeUnusedLevelsCommand; //============================================================================= @@ -112,22 +136,6 @@ class RemoveLevelCommand final : public MenuItemHandler { public: RemoveLevelCommand() : MenuItemHandler(MI_RemoveLevel) {} - bool removeLevel(TXshLevel *level) { - TApp *app = TApp::instance(); - ToonzScene *scene = app->getCurrentScene()->getScene(); - if (scene->getChildStack()->getTopXsheet()->isLevelUsed(level)) - DVGui::error( - QObject::tr("It is not possible to delete the used level %1.") - .arg(QString::fromStdWString( - level->getName()))); //"E_CantDeleteUsedLevel_%1" - else { - TUndoManager *um = TUndoManager::manager(); - um->add(new DeleteLevelUndo(level)); - scene->getLevelSet()->removeLevel(level); - } - return true; - } - void execute() override { TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet(); CastSelection *castSelection = @@ -142,7 +150,7 @@ public: } int count = 0; for (int i = 0; i < (int)levels.size(); i++) - if (removeLevel(levels[i])) count++; + if (LevelCmd::removeLevelFromCast(levels[i])) count++; if (count == 0) return; TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); TApp::instance()->getCurrentScene()->notifyCastChange(); @@ -522,7 +530,24 @@ void LevelCmd::addMissingLevelsToCast(std::set &levels) { std::wstring oldName = levelName; NameModifier nm(levelName); levelName = nm.getNext(); - while (levelSet->hasLevel(levelName)) levelName = nm.getNext(); + while (1) { + TXshLevel *existingLevel = levelSet->getLevel(levelName); + // if the level name is not used in the cast, nothing to do + if (!existingLevel) break; + // try if the existing level is unused in the xsheet and remove from the + // cast + else if (Preferences::instance()->isAutoRemoveUnusedLevelsEnabled() && + LevelCmd::removeLevelFromCast( + existingLevel, + TApp::instance()->getCurrentScene()->getScene(), false)) { + DVGui::info( + QObject::tr("Removed unused level %1 from the scene cast. (This " + "behavior can be disabled in Preferences.)") + .arg(QString::fromStdWString(levelName))); + break; + } + levelName = nm.getNext(); + } addLevelToCastUndo *undo = new addLevelToCastUndo(level, levelName, oldName); undo->m_isLastInRedoBlock = false; // prevent to emit signal diff --git a/toonz/sources/toonz/levelcommand.h b/toonz/sources/toonz/levelcommand.h index d14132f..bbcbb22 100644 --- a/toonz/sources/toonz/levelcommand.h +++ b/toonz/sources/toonz/levelcommand.h @@ -9,10 +9,23 @@ #include class TXshLevel; +class ToonzScene; namespace LevelCmd { void addMissingLevelsToCast(const QList& columns); void addMissingLevelsToCast(std::set& levels); + +// Remove all unused level from the scene cast. +// When there is no unused level, show an error message if showmessage==true. +// Return true if something is removed. +bool removeUnusedLevelsFromCast(bool showMessage = true); + +// Remove the level from the scene cast if it is not used in the xsheet. +// Return true if the level is unused and removed. +// When the level is used, an show error message if showMessage==true and +// returns false. +bool removeLevelFromCast(TXshLevel* level, ToonzScene* scene = nullptr, + bool showMessage = true); } // namespace LevelCmd -#endif +#endif \ No newline at end of file diff --git a/toonz/sources/toonz/levelcreatepopup.cpp b/toonz/sources/toonz/levelcreatepopup.cpp index 527012a..ef9326e 100644 --- a/toonz/sources/toonz/levelcreatepopup.cpp +++ b/toonz/sources/toonz/levelcreatepopup.cpp @@ -5,6 +5,7 @@ // Tnz6 includes #include "menubarcommandids.h" #include "tapp.h" +#include "levelcommand.h" // TnzTools includes #include "tools/toolhandle.h" @@ -34,6 +35,7 @@ #include "toonz/palettecontroller.h" #include "toonz/tproject.h" #include "toonz/namebuilder.h" +#include "toonz/childstack.h" // TnzCore includes #include "tsystem.h" @@ -328,9 +330,16 @@ bool LevelCreatePopup::levelExists(std::wstring levelName) { .withParentDir(parentDir); actualFp = scene->decodeFilePath(fp); - if (levelSet->getLevel(levelName) != 0 || - TSystem::doesExistFileOrLevel(actualFp)) { + if (TSystem::doesExistFileOrLevel(actualFp)) return true; + else if (TXshLevel *level = levelSet->getLevel(levelName)) { + // even if the level exists in the scene cast, it can be replaced if it is + // unused + if (Preferences::instance()->isAutoRemoveUnusedLevelsEnabled() && + !scene->getChildStack()->getTopXsheet()->isLevelUsed(level)) + return false; + else + return true; } else return false; } @@ -470,12 +479,18 @@ bool LevelCreatePopup::apply() { int numFrames = step * (((to - from) / inc) + 1); - if (scene->getLevelSet()->getLevel(levelName)) { - error( - tr("The level name specified is already used: please choose a " - "different level name")); - m_nameFld->selectAll(); - return false; + TXshLevel *existingLevel = scene->getLevelSet()->getLevel(levelName); + if (existingLevel) { + // check if the existing level can be removed + if (!Preferences::instance()->isAutoRemoveUnusedLevelsEnabled() || + scene->getChildStack()->getTopXsheet()->isLevelUsed(existingLevel)) { + error( + tr("The level name specified is already used: please choose a " + "different level name")); + m_nameFld->selectAll(); + return false; + } + // if the exitingLevel is not null, it will be removed afterwards } TFilePath parentDir(m_pathFld->getPath().toStdWString()); @@ -509,6 +524,18 @@ bool LevelCreatePopup::apply() { } } + TUndoManager::manager()->beginBlock(); + + // existingLevel is not nullptr only if the level is unused AND + // the preference option AutoRemoveUnusedLevels is ON + if (existingLevel) { + bool ok = LevelCmd::removeLevelFromCast(existingLevel, scene, false); + assert(ok); + DVGui::info(QObject::tr("Removed unused level %1 from the scene cast. " + "(This behavior can be disabled in Preferences.)") + .arg(QString::fromStdWString(levelName))); + } + /*-- これからLevelを配置しようとしているセルが空いているかどうかのチェック * --*/ bool areColumnsShifted = false; @@ -544,6 +571,7 @@ bool LevelCreatePopup::apply() { TXshLevel *level = scene->createNewLevel(lType, levelName, TDimension(), 0, fp); TXshSimpleLevel *sl = dynamic_cast(level); + assert(sl); sl->setPath(fp, true); if (lType == TZP_XSHLEVEL || lType == OVL_XSHLEVEL) { @@ -582,6 +610,8 @@ bool LevelCreatePopup::apply() { undo->onAdd(sl); + TUndoManager::manager()->endBlock(); + app->getCurrentScene()->notifySceneChanged(); app->getCurrentScene()->notifyCastChange(); app->getCurrentXsheet()->notifyXsheetChanged(); diff --git a/toonz/sources/toonz/levelsettingspopup.cpp b/toonz/sources/toonz/levelsettingspopup.cpp index 4540711..7641588 100644 --- a/toonz/sources/toonz/levelsettingspopup.cpp +++ b/toonz/sources/toonz/levelsettingspopup.cpp @@ -10,6 +10,7 @@ #include "castselection.h" #include "fileselection.h" #include "columnselection.h" +#include "levelcommand.h" // TnzQt includes #include "toonzqt/menubarcommand.h" @@ -950,6 +951,18 @@ void LevelSettingsPopup::onNameChanged() { TLevelSet *levelSet = TApp::instance()->getCurrentScene()->getScene()->getLevelSet(); + TUndoManager::manager()->beginBlock(); + + // remove existing & unused level + if (Preferences::instance()->isAutoRemoveUnusedLevelsEnabled()) { + TXshLevel *existingLevel = levelSet->getLevel(text.toStdWString()); + if (existingLevel && + LevelCmd::removeLevelFromCast(existingLevel, nullptr, false)) + DVGui::info(QObject::tr("Removed unused level %1 from the scene cast. " + "(This behavior can be disabled in Preferences.)") + .arg(text)); + } + if (!levelSet->renameLevel(level, text.toStdWString())) { error("The name " + text + " you entered for the level is already used.\nPlease enter a " @@ -961,6 +974,8 @@ void LevelSettingsPopup::onNameChanged() { TUndoManager::manager()->add( new LevelSettingsUndo(level, LevelSettingsUndo::Name, oldName, text)); + TUndoManager::manager()->endBlock(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); TApp::instance()->getCurrentScene()->notifyCastChange(); } diff --git a/toonz/sources/toonz/preferencespopup.cpp b/toonz/sources/toonz/preferencespopup.cpp index 38347ce..8912707 100644 --- a/toonz/sources/toonz/preferencespopup.cpp +++ b/toonz/sources/toonz/preferencespopup.cpp @@ -1067,6 +1067,8 @@ QString PreferencesPopup::getUIString(PreferencesItemId id) { // Loading {importPolicy, tr("Default File Import Behavior:")}, {autoExposeEnabled, tr("Expose Loaded Levels in Xsheet")}, + {autoRemoveUnusedLevels, + tr("Automatically Remove Unused Levels From Scene Cast")}, {subsceneFolderEnabled, tr("Create Sub-folder when Importing Sub-Xsheet")}, {removeSceneNumberFromLoadedLevelName, @@ -1573,7 +1575,8 @@ QWidget* PreferencesPopup::createLoadingPage() { setupLayout(lay); insertUI(importPolicy, lay, getComboItemList(importPolicy)); - insertUI(autoExposeEnabled, lay); + QGridLayout* autoExposeLay = insertGroupBoxUI(autoExposeEnabled, lay); + { insertUI(autoRemoveUnusedLevels, autoExposeLay); } insertUI(subsceneFolderEnabled, lay); insertUI(removeSceneNumberFromLoadedLevelName, lay); insertUI(IgnoreImageDpi, lay); diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index 0852996..6f8388e 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -427,6 +427,9 @@ void Preferences::definePreferenceItems() { QMetaType::Int, 0); // On Demand define(columnIconLoadingPolicy, "columnIconLoadingPolicy", QMetaType::Int, (int)LoadAtOnce); + define(autoRemoveUnusedLevels, "autoRemoveUnusedLevels", QMetaType::Bool, + false); + //"levelFormats" need to be handle separately // Saving