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