diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h index 5278b51..957e7a1 100644 --- a/toonz/sources/include/toonz/preferences.h +++ b/toonz/sources/include/toonz/preferences.h @@ -421,6 +421,9 @@ public: bool isShowFrameNumberWithLettersEnabled() const { return getBoolValue(showFrameNumberWithLetters); } + bool isLinkColumnNameWithLevelEnabled() const { + return getBoolValue(linkColumnNameWithLevel); + } // Animation tab int getKeyframeType() const { return getIntValue(keyframeType); } diff --git a/toonz/sources/include/toonz/preferencesitemids.h b/toonz/sources/include/toonz/preferencesitemids.h index 70ce829..1dd6454 100644 --- a/toonz/sources/include/toonz/preferencesitemids.h +++ b/toonz/sources/include/toonz/preferencesitemids.h @@ -140,6 +140,7 @@ enum PreferencesItemId { currentColumnColor, levelNameDisplayType, showFrameNumberWithLetters, + linkColumnNameWithLevel, //---------- // Animation diff --git a/toonz/sources/include/toonz/txshlevelcolumn.h b/toonz/sources/include/toonz/txshlevelcolumn.h index e519264..6b50090 100644 --- a/toonz/sources/include/toonz/txshlevelcolumn.h +++ b/toonz/sources/include/toonz/txshlevelcolumn.h @@ -19,6 +19,7 @@ // forward declarations class TLevelColumnFx; class TXshCell; +class TXshLevel; //============================================================================= //! The TXshLevelColumn class provides a column of levels in xsheet and allows @@ -86,7 +87,8 @@ Return \b TFx. TFx *getFx() const override; // Used in TCellData::getNumbers - bool setNumbers(int row, int rowCount, const TXshCell cells[]); + bool setNumbers(int row, int rowCount, const TXshCell cells[], + TXshLevel *reservedLevel); private: // not implemented diff --git a/toonz/sources/toonz/celldata.cpp b/toonz/sources/toonz/celldata.cpp index 709f2fd..bee5fc1 100644 --- a/toonz/sources/toonz/celldata.cpp +++ b/toonz/sources/toonz/celldata.cpp @@ -11,6 +11,12 @@ #include "toonz/tcolumnfx.h" #include "toonz/fxdag.h" #include "toonz/txshlevelcolumn.h" +#include "toonz/preferences.h" +#include "toonz/tstageobject.h" +#include "tapp.h" +#include "toonz/tscenehandle.h" +#include "toonz/toonzscene.h" +#include "toonz/levelset.h" //----------------------------------------------------------------------------- @@ -106,15 +112,35 @@ bool TCellData::getCells(TXsheet *xsh, int r0, int c0, int &r1, int &c1, bool TCellData::getNumbers(TXsheet *xsh, int r0, int c0, int &r1, int &c1) const { - r1 = r0 + m_rowCount - 1; - bool oneToMulti = m_colCount == 1 && c0 < c1; + r1 = r0 + m_rowCount - 1; + bool oneToMulti = m_colCount == 1 && c0 < c1; if (!oneToMulti) c1 = c0 + m_colCount - 1; bool cellSet = false; for (int c = c0; c <= c1; c++) { - TXshColumn *column = xsh->getColumn(c); - if (!column || column->isEmpty()) continue; + TXshColumn *column = xsh->getColumn(c); + TXshLevel *reservedLevel = nullptr; + if (!column) continue; + if (column->isEmpty()) { + if (Preferences::instance()->isLinkColumnNameWithLevelEnabled() && + xsh->getStageObject(TStageObjectId::ColumnId(c)) + ->hasSpecifiedName()) { + std::string columnName = + xsh->getStageObject(TStageObjectId::ColumnId(c))->getName(); + ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene(); + TLevelSet *levelSet = scene->getLevelSet(); + reservedLevel = levelSet->getLevel(to_wstring(columnName)); + if (!reservedLevel) continue; + // connect to xsheet + if (!column->getPaletteColumn()) { + TFx *fx = column->getFx(); + if (fx) xsh->getFxDag()->addToXsheet(fx); + } + + } else + continue; + } TXshLevelColumn *levelColumn = column->getLevelColumn(); if (!levelColumn) continue; @@ -125,8 +151,8 @@ bool TCellData::getNumbers(TXsheet *xsh, int r0, int c0, int &r1, if (!canChange(column, sourceColIndex)) continue; - bool isSet = levelColumn->setNumbers(r0, m_rowCount, - &cells[sourceColIndex * m_rowCount]); + bool isSet = levelColumn->setNumbers( + r0, m_rowCount, &cells[sourceColIndex * m_rowCount], reservedLevel); cellSet = cellSet || isSet; } @@ -194,7 +220,7 @@ void TCellData::cloneZeraryFx(int index, std::vector &cells) const { newFxLevel->setColumn(newFxColumn); // replace the zerary fx cells by the new fx int r; - for (r = firstNotEmptyIndex; r < (index + 1) * m_rowCount; r++) + for (r = firstNotEmptyIndex; r < (index + 1) * m_rowCount; r++) cells[r] = TXshCell(newFxLevel, m_cells[r].getFrameId()); } } diff --git a/toonz/sources/toonz/cellselection.cpp b/toonz/sources/toonz/cellselection.cpp index 73aa2ec..0f5c6f1 100644 --- a/toonz/sources/toonz/cellselection.cpp +++ b/toonz/sources/toonz/cellselection.cpp @@ -58,6 +58,7 @@ #include "tools/strokeselection.h" #include "toonz/sceneproperties.h" #include "toutputproperties.h" +#include "toonz/tstageobjectcmd.h" // TnzCore includes #include "timagecache.h" @@ -217,13 +218,17 @@ class PasteCellsUndo final : public TUndo { TCellData *m_data; std::vector m_areOldColumnsEmpty; bool m_containsSoundColumn; + std::map m_oldEmptyColumnNames; public: PasteCellsUndo(int r0, int c0, int r1, int c1, int oldR0, int oldC0, int oldR1, int oldC1, const std::vector &areColumnsEmpty, - bool containsSoundColumn) + bool containsSoundColumn, + const std::map &oldEmptyColumnNames = + std::map()) : m_areOldColumnsEmpty(areColumnsEmpty) - , m_containsSoundColumn(containsSoundColumn) { + , m_containsSoundColumn(containsSoundColumn) + , m_oldEmptyColumnNames(oldEmptyColumnNames) { m_data = new TCellData(); TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); m_data->setCells(xsh, r0, c0, r1, c1); @@ -246,13 +251,18 @@ public: int oldR0, oldC0, oldR1, oldC1; m_oldSelection->getSelectedCells(oldR0, oldC0, oldR1, oldC1); + TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + for (const auto &item : m_oldEmptyColumnNames) { + xsh->getStageObject(TStageObjectId::ColumnId(item.first)) + ->setName(item.second); + } + int c0BeforeCut = c0; int c1BeforeCut = c1; // Cut delle celle che sono in newSelection cutCellsWithoutUndo(r0, c0, r1, c1); // Se le colonne erano vuote le resetto (e' necessario farlo per le colonne // particolari, colonne sount o palette) - TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); assert(c1BeforeCut - c0BeforeCut + 1 == (int)m_areOldColumnsEmpty.size()); int c; for (c = c0BeforeCut; c <= c1BeforeCut; c++) { @@ -1868,6 +1878,23 @@ void TCellSelection::pasteCells() { return; } + // pasting cells may change empty column names. So keep old column names for + // undo + std::map emptyColumnNames; + if (Preferences::instance()->isLinkColumnNameWithLevelEnabled()) { + for (int c = c0; c < c0 + cellData->getColCount(); c++) { + TXshColumn *column = xsh->getColumn(c); + if (!column || column->isEmpty()) { + if (!xsh->getStageObject(TStageObjectId::ColumnId(c)) + ->hasSpecifiedName()) + emptyColumnNames[c] = ""; + else + emptyColumnNames[c] = + xsh->getStageObject(TStageObjectId::ColumnId(c))->getName(); + } + } + } + bool isPaste = pasteCellsWithoutUndo(cellData, r0, c0, r1, c1); // Se la selezione corrente e' TCellSelection selezione le celle copiate @@ -1905,9 +1932,9 @@ void TCellSelection::pasteCells() { getLevelSetFromData(cellData, pastedLevels); LevelCmd::addMissingLevelsToCast(pastedLevels); - TUndoManager::manager()->add( - new PasteCellsUndo(r0, c0, r1, c1, oldR0, oldC0, oldR1, oldC1, - areColumnsEmpty, containsSoundColumn)); + TUndoManager::manager()->add(new PasteCellsUndo( + r0, c0, r1, c1, oldR0, oldC0, oldR1, oldC1, areColumnsEmpty, + containsSoundColumn, emptyColumnNames)); TApp::instance()->getCurrentScene()->setDirtyFlag(true); if (containsSoundColumn) TApp::instance()->getCurrentXsheet()->notifyXsheetSoundChanged(); @@ -2530,8 +2557,20 @@ void TCellSelection::deleteCells(bool withShift) { TUndoManager::manager()->beginBlock(); // remove, then insert empty column for (auto colId : removedColIds) { + // keep column name if specified + std::string columnName = ""; + if (Preferences::instance()->isLinkColumnNameWithLevelEnabled() && + xsh->getStageObject(TStageObjectId::ColumnId(colId)) + ->hasSpecifiedName()) + columnName = + xsh->getStageObject(TStageObjectId::ColumnId(colId))->getName(); + ColumnCmd::deleteColumn(colId, true); ColumnCmd::insertEmptyColumn(colId); + + if (!columnName.empty()) + TStageObjectCmd::rename(TStageObjectId::ColumnId(colId), columnName, + TApp::instance()->getCurrentXsheet()); } } diff --git a/toonz/sources/toonz/preferencespopup.cpp b/toonz/sources/toonz/preferencespopup.cpp index f9103db..1c5d256 100644 --- a/toonz/sources/toonz/preferencespopup.cpp +++ b/toonz/sources/toonz/preferencespopup.cpp @@ -1306,6 +1306,7 @@ QString PreferencesPopup::getUIString(PreferencesItemId id) { {levelNameDisplayType, tr("Level Name Display:")}, {showFrameNumberWithLetters, tr("Show \"ABC\" Appendix to the Frame Number in Xsheet Cell")}, + {linkColumnNameWithLevel, tr("Link Column Name with Level")}, // Animation {keyframeType, tr("Default Interpolation:")}, @@ -2013,6 +2014,7 @@ QWidget* PreferencesPopup::createXsheetPage() { insertUI(xsheetLayoutPreference, lay, getComboItemList(xsheetLayoutPreference)); insertUI(levelNameDisplayType, lay, getComboItemList(levelNameDisplayType)); + insertUI(linkColumnNameWithLevel, lay); insertUI(xsheetStep, lay); insertUI(xsheetAutopanEnabled, lay); insertUI(DragCellsBehaviour, lay, getComboItemList(DragCellsBehaviour)); @@ -2048,6 +2050,15 @@ QWidget* PreferencesPopup::createXsheetPage() { unifyColumnVisibilityToggles, &PreferencesPopup::onUnifyColumnVisibilityTogglesChanged); + QCheckBox* linkColumnNameWithLevelCheck = + getUI(linkColumnNameWithLevel); + linkColumnNameWithLevelCheck->setToolTip( + tr("This option will do the following:\n" + "- When setting a cell in the empty column, level name will be copied " + "to the column name\n" + "- Typing the cell without level name in the empty column will try to " + "use a level with the same name as the column\n" + "The behavior may be changed in the future development.")); return widget; } diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index bf27a8b..9679993 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -76,7 +76,8 @@ namespace { -const bool checkContainsSingleLevel(TXshColumn *column) { +const bool checkContainsSingleLevel(TXshColumn *column, + const std::string &columnName) { TXshLevel *level = nullptr; TXshCellColumn *cellColumn = column->getCellColumn(); if (cellColumn) { @@ -91,7 +92,9 @@ const bool checkContainsSingleLevel(TXshColumn *column) { else if (lvl != level) return false; } - return level != nullptr; + // column name should be unspecified or same as the content level + return level != nullptr && + (columnName.empty() || level->getName() == to_wstring(columnName)); } return false; } @@ -856,6 +859,26 @@ void RenameCellField::renameCell() { // previous frames // (when editing not empty column) if (xsheet->isColumnEmpty(c)) { + // find a level with name same as the column (if the preferences option + // is set to do so) + if (Preferences::instance()->isLinkColumnNameWithLevelEnabled() && + xsheet->getStageObject(TStageObjectId::ColumnId(c)) + ->hasSpecifiedName()) { + std::string columnName = + xsheet->getStageObject(TStageObjectId::ColumnId(c))->getName(); + TLevelSet *levelSet = scene->getLevelSet(); + TXshLevel *xl = levelSet->getLevel(to_wstring(columnName)); + TXshSimpleLevel *sl = (xl) ? xl->getSimpleLevel() : nullptr; + if (sl && + (!sl->isEmpty() || sl->getFirstFid() != TFrameId::NO_FRAME)) { + sl->formatFId(fid, tmplFId); + cells.append(TXshCell(xl, fid)); + changed = true; + hasFrameZero = (fid.getNumber() == 0 && xl->getSimpleLevel() && + xl->getSimpleLevel()->isFid(fid)); + continue; + } + } cells.append(TXshCell()); continue; } @@ -1270,10 +1293,16 @@ void CellArea::drawCells(QPainter &p, const QRect toBeUpdated) { isPaletteColumn = column->getPaletteColumn() != 0; isSoundTextColumn = column->getSoundTextColumn() != 0; if (Preferences::instance()->getLevelNameDisplayType() == - Preferences::ShowLevelNameOnColumnHeader) + Preferences::ShowLevelNameOnColumnHeader) { + std::string columnName = ""; + if (col >= 0 && xsh->getStageObject(TStageObjectId::ColumnId(col)) + ->hasSpecifiedName()) + columnName = + xsh->getStageObject(TStageObjectId::ColumnId(col))->getName(); showLevelName = (isSoundColumn || isPaletteColumn || isSoundTextColumn || - !checkContainsSingleLevel(column)); + !checkContainsSingleLevel(column, columnName)); + } } // check if the column is reference bool isReference = true; diff --git a/toonz/sources/toonz/xshcolumnviewer.cpp b/toonz/sources/toonz/xshcolumnviewer.cpp index bc153fa..c3b0416 100644 --- a/toonz/sources/toonz/xshcolumnviewer.cpp +++ b/toonz/sources/toonz/xshcolumnviewer.cpp @@ -49,6 +49,7 @@ #include "toonz/tfxhandle.h" #include "toonz/tcamera.h" #include "toonz/tcolumnhandle.h" +#include "toonz/levelset.h" // TnzCore includes #include "tconvert.h" @@ -707,12 +708,18 @@ void RenameColumnField::renameColumn() { : TStageObjectId::CameraId(cameraIndex); TXshColumn *column = m_xsheetHandle->getXsheet()->getColumn(columnId.getIndex()); + if (!column && m_col >= 0) { + m_xsheetHandle->getXsheet()->insertColumn(m_col); + column = m_xsheetHandle->getXsheet()->getColumn(columnId.getIndex()); + } + TXshZeraryFxColumn *zColumn = dynamic_cast(column); if (zColumn) TFxCommand::renameFx(zColumn->getZeraryColumnFx(), ::to_wstring(newName), m_xsheetHandle); else TStageObjectCmd::rename(columnId, newName, m_xsheetHandle); + m_xsheetHandle->notifyXsheetChanged(); m_col = -1; setText(""); @@ -745,7 +752,7 @@ const bool ColumnArea::isControlPressed() { return isCtrlPressed; } //----------------------------------------------------------------------------- ColumnArea::DrawHeader::DrawHeader(ColumnArea *nArea, QPainter &nP, int nCol) - : area(nArea), p(nP), col(nCol) { + : area(nArea), p(nP), col(nCol), reservedLevel(nullptr) { m_viewer = area->m_viewer; o = m_viewer->orientation(); app = TApp::instance(); @@ -753,6 +760,18 @@ ColumnArea::DrawHeader::DrawHeader(ColumnArea *nArea, QPainter &nP, int nCol) column = xsh->getColumn(col); isEmpty = col >= 0 ? xsh->isColumnEmpty(col) : false; + if (isEmpty && Preferences::instance()->isLinkColumnNameWithLevelEnabled() && + m_viewer->getXsheet() + ->getStageObject(TStageObjectId::ColumnId(col)) + ->hasSpecifiedName()) { + std::string columnName = m_viewer->getXsheet() + ->getStageObject(TStageObjectId::ColumnId(col)) + ->getName(); + ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene(); + TLevelSet *levelSet = scene->getLevelSet(); + reservedLevel = levelSet->getLevel(to_wstring(columnName)); + } + TStageObjectId currentColumnId = app->getCurrentObject()->getObjectId(); // check if the column is current @@ -816,7 +835,11 @@ void ColumnArea::DrawHeader::levelColors(QColor &columnColor, if (column->isRendered() || column->getMeshColumn()) usage = Normal; } - if (usage == Reference) { + if (reservedLevel) { + int ltype; + m_viewer->getCellTypeAndColors(ltype, columnColor, dragColor, + TXshCell(reservedLevel, TFrameId())); + } else if (usage == Reference) { columnColor = m_viewer->getReferenceColumnColor(); dragColor = m_viewer->getReferenceColumnBorderColor(); } else @@ -859,15 +882,17 @@ void ColumnArea::DrawHeader::drawBaseFill(const QColor &columnColor, // Fill base color, in timeline view adjust it right upto thumbnail so column // head color doesn't show under icon switches. - if (isEmpty) + if (isEmpty && !reservedLevel) p.fillRect(o->isVerticalTimeline() ? rect : rect.adjusted(80, 0, 0, 0), m_viewer->getEmptyColumnHeadColor()); else if (col < 0) p.fillRect(o->isVerticalTimeline() ? rect : rect.adjusted(80, 0, 0, 0), columnColor); else { + QBrush brush(columnColor, + (reservedLevel) ? Qt::DiagCrossPattern : Qt::SolidPattern); p.fillRect(o->isVerticalTimeline() ? rect : rect.adjusted(80, 0, 0, 0), - columnColor); + brush); if (o->flag(PredefinedFlag::DRAG_LAYER_VISIBLE)) { // column handle @@ -2787,7 +2812,10 @@ void ColumnArea::mouseDoubleClickEvent(QMouseEvent *event) { if (!nameRect.contains(mouseInCell)) return; TXsheet *xsh = m_viewer->getXsheet(); - if (col >= 0 && xsh->isColumnEmpty(col)) return; + // enable to rename empty column when the column name is linked to the level + if (!Preferences::instance()->isLinkColumnNameWithLevelEnabled() && + col >= 0 && xsh->isColumnEmpty(col)) + return; QPoint fieldPos = (col < 0 && o->isVerticalTimeline()) ? nameRect.topLeft() : topLeft; diff --git a/toonz/sources/toonz/xshcolumnviewer.h b/toonz/sources/toonz/xshcolumnviewer.h index cde5a22..ea091b6 100644 --- a/toonz/sources/toonz/xshcolumnviewer.h +++ b/toonz/sources/toonz/xshcolumnviewer.h @@ -321,6 +321,7 @@ class ColumnArea final : public QWidget { bool isEmpty, isCurrent; TXshColumn *column; QPoint orig; + TXshLevel *reservedLevel; public: DrawHeader(ColumnArea *area, QPainter &p, int col); diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index cef5eae..fd28142 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -604,6 +604,14 @@ void Preferences::definePreferenceItems() { 0); // default define(showFrameNumberWithLetters, "showFrameNumberWithLetters", QMetaType::Bool, false); + // This option will do the following: + // - When setting a cell in the empty column, level name will be copied to the + // column name + // - Typing the cell without level name in the empty column will try to use a + // level with the same name as the column The behavior may be changed in the + // future development. + define(linkColumnNameWithLevel, "linkColumnNameWithLevel", QMetaType::Bool, + false); // Animation define(keyframeType, "keyframeType", QMetaType::Int, 2); // Linear diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index 4484322..79f139e 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -302,6 +302,13 @@ bool TXsheet::setCell(int row, int col, const TXshCell &cell) { else if (row >= m_imp->m_frameCount) m_imp->m_frameCount = row + 1; + // set the level name to the column + if (wasColumnEmpty && cellColumn && !cell.isEmpty() && + Preferences::instance()->isLinkColumnNameWithLevelEnabled()) { + getStageObject(TStageObjectId::ColumnId(col)) + ->setName(to_string(cell.m_level->getName())); + } + TNotifier::instance()->notify(TXsheetChange()); return true; @@ -386,6 +393,14 @@ bool TXsheet::setCells(int row, int col, int rowCount, const TXshCell cells[]) { newColRowCount < m_imp->m_frameCount) updateFrameCount(); } + row + 1; + + // set the level name to the column + if (wasColumnEmpty && i < rowCount && + Preferences::instance()->isLinkColumnNameWithLevelEnabled()) { + getStageObject(TStageObjectId::ColumnId(col)) + ->setName(to_string(cells[i].m_level->getName())); + } return true; } diff --git a/toonz/sources/toonzlib/txshlevelcolumn.cpp b/toonz/sources/toonzlib/txshlevelcolumn.cpp index 7774465..da2a46f 100644 --- a/toonz/sources/toonzlib/txshlevelcolumn.cpp +++ b/toonz/sources/toonzlib/txshlevelcolumn.cpp @@ -209,9 +209,12 @@ void TXshLevelColumn::saveData(TOStream &os) { //----------------------------------------------------------------------------- // Used in TCellData::getNumbers -bool TXshLevelColumn::setNumbers(int row, int rowCount, - const TXshCell cells[]) { - if (m_cells.empty()) return false; +// reservedLevel can be nonzero if the preferences option +// "LinkColumnNameWithLevel" is ON and the column name is the same as some level +// in the scene cast. +bool TXshLevelColumn::setNumbers(int row, int rowCount, const TXshCell cells[], + TXshLevel *reservedLevel) { + if (m_cells.empty() && !reservedLevel) return false; // Check availability. // - if source cells are all empty, do nothing // - also, if source or target cells contain NO_FRAME, do nothing @@ -236,16 +239,18 @@ bool TXshLevelColumn::setNumbers(int row, int rowCount, // Find a level to input. // If the first target cell is empty, search the upper cells, and lower cells // and use a level of firsty-found occupied neighbor cell. - TXshLevelP currentLevel; - int tmpIndex = std::min(row - m_first, (int)m_cells.size() - 1); + TXshLevelP currentLevel = reservedLevel; + int tmpIndex = std::min(row - m_first, (int)m_cells.size() - 1); // search upper cells - while (tmpIndex >= 0) { - TXshCell tmpCell = m_cells[tmpIndex]; - if (!tmpCell.isEmpty() && tmpCell.m_frameId != TFrameId::NO_FRAME) { - currentLevel = tmpCell.m_level; - break; + if (!currentLevel) { + while (tmpIndex >= 0) { + TXshCell tmpCell = m_cells[tmpIndex]; + if (!tmpCell.isEmpty() && tmpCell.m_frameId != TFrameId::NO_FRAME) { + currentLevel = tmpCell.m_level; + break; + } + tmpIndex--; } - tmpIndex--; } // if not found any level in upper cells, then search the lower cells if (!currentLevel) {