diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h
index 4f8f1d2..0ab37f4 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 39ccc05..33876e0 100644
--- a/toonz/sources/include/toonz/preferencesitemids.h
+++ b/toonz/sources/include/toonz/preferencesitemids.h
@@ -57,6 +57,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 10c3897..55e92ad 100644
--- a/toonz/sources/include/toonz/toonzscene.h
+++ b/toonz/sources/include/toonz/toonzscene.h
@@ -286,6 +286,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 550d9df..8ee14b1 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
//---------------------------------------------------------------------------
@@ -875,6 +907,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) +
@@ -1365,6 +1402,7 @@ void IoCmd::newScene() {
bool IoCmd::saveScene(const TFilePath &path, int flags) {
bool overwrite = (flags & SILENTLY_OVERWRITE) != 0;
bool saveSubxsheet = (flags & SAVE_SUBXSHEET) != 0;
+ bool isAutosave = (flags & AUTO_SAVE) != 0;
TApp *app = TApp::instance();
assert(!path.isEmpty());
@@ -1404,6 +1442,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 && !isAutosave &&
+ 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,
@@ -1510,7 +1557,7 @@ bool IoCmd::saveScene(const TFilePath &path, int flags) {
// IoCmd::saveScene()
//---------------------------------------------------------------------------
-bool IoCmd::saveScene() {
+bool IoCmd::saveScene(int flags) {
TSelection *oldSelection =
TApp::instance()->getCurrentSelection()->getSelection();
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
@@ -1528,7 +1575,7 @@ bool IoCmd::saveScene() {
} else {
TFilePath fp = scene->getScenePath();
// salva la scena con il nome fp. se fp esiste gia' lo sovrascrive
- return saveScene(fp, SILENTLY_OVERWRITE);
+ return saveScene(fp, SILENTLY_OVERWRITE | flags);
}
}
@@ -1673,10 +1720,10 @@ bool IoCmd::saveLevel(TXshSimpleLevel *sl) {
// IoCmd::saveAll()
//---------------------------------------------------------------------------
-bool IoCmd::saveAll() {
+bool IoCmd::saveAll(int flags) {
// try to save as much as possible
// if anything is wrong, return false
- bool result = saveScene();
+ bool result = saveScene(flags);
TApp *app = TApp::instance();
ToonzScene *scene = app->getCurrentScene()->getScene();
diff --git a/toonz/sources/toonz/iocommand.h b/toonz/sources/toonz/iocommand.h
index 6348782..b72a854 100644
--- a/toonz/sources/toonz/iocommand.h
+++ b/toonz/sources/toonz/iocommand.h
@@ -186,6 +186,7 @@ bool loadSubScene(const TFilePath &scenePath);
enum SaveSceneFlags {
SILENTLY_OVERWRITE = 0x1,
SAVE_SUBXSHEET = 0x2,
+ AUTO_SAVE = 0x4
};
// ritorna true sse la scena e' stata salvata
@@ -196,7 +197,7 @@ enum SaveSceneFlags {
// 0 salva comunque
// tutta la scena, altrimenti solo il sottoxsheet
bool saveScene(const TFilePath &fp, int flags);
-bool saveScene();
+bool saveScene(int flags = 0);
bool saveLevel(const TFilePath &fp);
bool saveLevel();
@@ -204,7 +205,7 @@ bool saveLevel();
bool saveLevel(const TFilePath &fp, TXshSimpleLevel *sl, bool overwrite);
bool saveLevel(TXshSimpleLevel *sl);
-bool saveAll();
+bool saveAll(int flags = 0);
void saveNonSceneFiles();
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 3824326..32ffa70 100644
--- a/toonz/sources/toonz/preferencespopup.cpp
+++ b/toonz/sources/toonz/preferencespopup.cpp
@@ -1068,6 +1068,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,
@@ -1579,7 +1581,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/toonz/tapp.cpp b/toonz/sources/toonz/tapp.cpp
index 1565477..0f6030c 100644
--- a/toonz/sources/toonz/tapp.cpp
+++ b/toonz/sources/toonz/tapp.cpp
@@ -706,9 +706,9 @@ void TApp::autosave() {
pb.show();
Preferences *pref = Preferences::instance();
if (pref->isAutosaveSceneEnabled() && pref->isAutosaveOtherFilesEnabled()) {
- IoCmd::saveAll();
+ IoCmd::saveAll(IoCmd::AUTO_SAVE);
} else if (pref->isAutosaveSceneEnabled()) {
- IoCmd::saveScene();
+ IoCmd::saveScene(IoCmd::AUTO_SAVE);
} else if (pref->isAutosaveOtherFilesEnabled()) {
IoCmd::saveNonSceneFiles();
}
diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp
index 8e7f73f..ee1b157 100644
--- a/toonz/sources/toonzlib/preferences.cpp
+++ b/toonz/sources/toonzlib/preferences.cpp
@@ -464,6 +464,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