From fecb529b459ad4c539f0eda5f22dbc5cb4dad6d4 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 29 Nov 2021 01:35:43 +0900 Subject: [PATCH 01/18] fix crash on creating macrofx --- toonz/sources/toonz/insertfxpopup.cpp | 81 +++++++++++++-------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/toonz/sources/toonz/insertfxpopup.cpp b/toonz/sources/toonz/insertfxpopup.cpp index 136f8473..a2e497b8 100644 --- a/toonz/sources/toonz/insertfxpopup.cpp +++ b/toonz/sources/toonz/insertfxpopup.cpp @@ -88,52 +88,51 @@ TFx *createPresetFxByName(TFilePath path) { } //----------------------------------------------------------------------------- +// same as createMacroFxByPath() in addfxcontextmenu.cpp TFx *createMacroFxByPath(TFilePath path) { - TIStream is(path); - TPersist *p = 0; - is >> p; - TMacroFx *fx = dynamic_cast(p); - if (!fx) return 0; - fx->setName(path.getWideName()); - // Assign a unic ID to each fx in the macro! - TApp *app = TApp::instance(); - TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); - if (!xsh) return fx; - FxDag *fxDag = xsh->getFxDag(); - if (!fxDag) return fx; - std::vector fxs; - fxs = fx->getFxs(); - QMap oldNewId; - int i; - for (i = 0; i < fxs.size(); i++) { - std::wstring oldId = fxs[i]->getFxId(); - fxDag->assignUniqueId(fxs[i].getPointer()); - oldNewId[oldId] = fxs[i]->getFxId(); - } + try { + TIStream is(path); + TPersist *p = 0; + is >> p; + TMacroFx *fx = dynamic_cast(p); + if (!fx) return 0; + fx->setName(path.getWideName()); + // Assign a unic ID to each fx in the macro! + TApp *app = TApp::instance(); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + if (!xsh) return fx; + FxDag *fxDag = xsh->getFxDag(); + if (!fxDag) return fx; + std::vector fxs; + fxs = fx->getFxs(); + QMap oldNewId; + int i; + for (i = 0; i < fxs.size(); i++) { + std::wstring oldId = fxs[i]->getFxId(); + fxDag->assignUniqueId(fxs[i].getPointer()); + std::wstring newId = fxs[i]->getFxId(); + oldNewId[oldId] = newId; - QStack> newPortNames; + // changing the id of the internal effects of a macro breaks the links + // between the name of the port and the port to which it is linked : + // I have to change the names of the ports and remap them within the macro + int j; + for (j = 0; j < fx->getInputPortCount(); j++) { + QString inputName = QString::fromStdString(fx->getInputPortName(j)); + if (inputName.endsWith(QString::fromStdWString(oldId))) { + QString newInputName = inputName; + newInputName.replace(QString::fromStdWString(oldId), + QString::fromStdWString(newId)); + fx->renamePort(inputName.toStdString(), newInputName.toStdString()); + } + } + } - // Devo cambiare il nome alle porte: contengono l'id dei vecchi effetti - for (i = fx->getInputPortCount() - 1; i >= 0; i--) { - std::string oldPortName = fx->getInputPortName(i); - std::string inFxOldId = oldPortName; - inFxOldId.erase(0, inFxOldId.find_last_of("_") + 1); - assert(oldNewId.contains(::to_wstring(inFxOldId))); - std::string inFxNewId = ::to_string(oldNewId[ ::to_wstring(inFxOldId)]); - std::string newPortName = oldPortName; - newPortName.erase(newPortName.find_last_of("_") + 1, - newPortName.size() - 1); - newPortName.append(inFxNewId); - TFxPort *fxPort = fx->getInputPort(i); - newPortNames.append(QPair(newPortName, fxPort)); - fx->removeInputPort(oldPortName); + return fx; + } catch (...) { + return 0; } - while (!newPortNames.isEmpty()) { - QPair newPort = newPortNames.pop(); - fx->addInputPort(newPort.first, *newPort.second); - } - return fx; } } // anonymous namespace From 866d31d07ce7dac0f04c352b2d3128f95f8addfe Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Wed, 1 Dec 2021 10:41:27 +0900 Subject: [PATCH 02/18] fix psd loading and level format preferences --- toonz/sources/toonz/iocommand.cpp | 14 +++++++------- toonz/sources/toonzlib/preferences.cpp | 9 ++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/toonz/sources/toonz/iocommand.cpp b/toonz/sources/toonz/iocommand.cpp index 81d00013..3705a5fe 100644 --- a/toonz/sources/toonz/iocommand.cpp +++ b/toonz/sources/toonz/iocommand.cpp @@ -2253,7 +2253,7 @@ static int loadPSDResource(IoCmd::LoadResourceArguments &args, assert(childLevel); childXsh = childLevel->getXsheet(); } - int subCol0 = args.col0; + int subCol0 = 0; loadedPsdLevelIndex.clear(); // for each layer in psd for (int i = 0; i < popup->getPsdLevelCount(); i++) { @@ -2265,7 +2265,7 @@ static int loadPSDResource(IoCmd::LoadResourceArguments &args, count += createSubXSheetFromPSDFolder(args, childXsh, subCol0, i, popup); else - count += createSubXSheetFromPSDFolder(args, xsh, subCol0, i, popup); + count += createSubXSheetFromPSDFolder(args, xsh, col0, i, popup); } else { TFilePath psdpath = popup->getPsdPath(i); TXshLevel *xl = 0; @@ -2280,14 +2280,14 @@ static int loadPSDResource(IoCmd::LoadResourceArguments &args, // lo importo nell'xsheet if (popup->subxsheet() && childXsh) { childXsh->exposeLevel(0, subCol0, xl); + subCol0++; + } else { + // move the current column to the right + col0++; + app->getCurrentColumn()->setColumnIndex(col0); } args.loadedLevels.push_back(xl); - subCol0++; count++; - - // move the current column to the right - col0++; - app->getCurrentColumn()->setColumnIndex(col0); } } } diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index 2791c3d1..01cbe953 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -94,7 +94,7 @@ inline bool formatLess(const Preferences::LevelFormat &a, //================================================================= void getDefaultLevelFormats(LevelFormatVector &lfv) { - lfv.resize(3); + lfv.resize(2); { LevelFormat &lf = lfv[0]; @@ -215,6 +215,13 @@ void getValue(QSettings &settings, ++it; } changed = true; + } + // remove the "empty" condition which may inserted due to the previous bug + else if ((*it).m_name.isEmpty() && + (*it).m_pathFormat == QRegExp(".*", Qt::CaseInsensitive) && + (*it).m_priority == 1 && (*it).m_options == LevelOptions()) { + it = lfv.erase(it); + changed = true; } else ++it; } From fe105a05cba1690fdf536167ee2cfb4cc85e6751 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Fri, 6 Aug 2021 18:23:11 +0900 Subject: [PATCH 03/18] enable frame suffix in camera capture --- .../include/toonz/filepathproperties.h | 49 +++++++++++++++ toonz/sources/stopmotion/stopmotion.cpp | 16 +++-- .../stopmotion/stopmotioncontroller.cpp | 61 +++++++++++-------- .../sources/stopmotion/stopmotioncontroller.h | 12 ++-- .../toonz/autoinputcellnumberpopup.cpp | 8 +-- toonz/sources/toonzlib/filepathproperties.cpp | 38 ++++++++++++ 6 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 toonz/sources/include/toonz/filepathproperties.h create mode 100644 toonz/sources/toonzlib/filepathproperties.cpp diff --git a/toonz/sources/include/toonz/filepathproperties.h b/toonz/sources/include/toonz/filepathproperties.h new file mode 100644 index 00000000..4c61ac96 --- /dev/null +++ b/toonz/sources/include/toonz/filepathproperties.h @@ -0,0 +1,49 @@ +#pragma once + +#ifndef FILEPATHPROPERTIES_H +#define FILEPATHPROPERTIES_H + +#include "tcommon.h" + +#undef DVAPI +#undef DVVAR +#ifdef TOONZLIB_EXPORTS +#define DVAPI DV_EXPORT_API +#define DVVAR DV_EXPORT_VAR +#else +#define DVAPI DV_IMPORT_API +#define DVVAR DV_IMPORT_VAR +#endif +//============================================================================= +// forward declarations +class TIStream; +class TOStream; + +//============================================================================= +// FilePathProperties +// This class defines file path condition for sequential image level + +class DVAPI FilePathProperties { + bool m_useStandard; + bool m_acceptNonAlphabetSuffix; + int m_letterCountForSuffix; + +public: + FilePathProperties(); + + bool useStandard() { return m_useStandard; } + void setUseStandard(bool on) { m_useStandard = on; } + + bool acceptNonAlphabetSuffix() { return m_acceptNonAlphabetSuffix; } + void setAcceptNonAlphabetSuffix(bool on) { m_acceptNonAlphabetSuffix = on; } + + int letterCountForSuffix() { return m_letterCountForSuffix; } + void setLetterCountForSuffix(int val) { m_letterCountForSuffix = val; } + + void saveData(TOStream& os) const; + void loadData(TIStream& is); + + bool isDefault(); +}; + +#endif \ No newline at end of file diff --git a/toonz/sources/stopmotion/stopmotion.cpp b/toonz/sources/stopmotion/stopmotion.cpp index 32067a76..2fab24c6 100644 --- a/toonz/sources/stopmotion/stopmotion.cpp +++ b/toonz/sources/stopmotion/stopmotion.cpp @@ -160,19 +160,25 @@ QString fidsToString(const std::vector &fids, } else { bool beginBlock = true; for (int f = 0; f < fids.size() - 1; f++) { - int num = fids[f].getNumber(); - int next_num = fids[f + 1].getNumber(); - if (num + 1 == next_num) { + int num = fids[f].getNumber(); + char letter = fids[f].getLetter(); + int next_num = fids[f + 1].getNumber(); + char next_letter = fids[f + 1].getLetter(); + + if (num + 1 == next_num && letter == '\0' && next_letter == '\0') { if (beginBlock) { retStr += QString::number(num) + " - "; beginBlock = false; } } else { - retStr += QString::number(num) + ", "; + retStr += QString::number(num); + if (letter != '\0') retStr += QString(letter); + retStr += ", "; beginBlock = true; } } - retStr += QString::number(fids.back().getNumber()); + if (fids.back().getLetter() != '\0') + retStr += QString(fids.back().getLetter()); } return retStr; } diff --git a/toonz/sources/stopmotion/stopmotioncontroller.cpp b/toonz/sources/stopmotion/stopmotioncontroller.cpp index 53b54be1..868a6795 100644 --- a/toonz/sources/stopmotion/stopmotioncontroller.cpp +++ b/toonz/sources/stopmotion/stopmotioncontroller.cpp @@ -666,42 +666,44 @@ void StopMotionSaveInFolderPopup::updateParentFolder() { //============================================================================= -FrameNumberLineEdit::FrameNumberLineEdit(QWidget *parent, int value) +FrameNumberLineEdit::FrameNumberLineEdit(QWidget* parent, TFrameId fId, + bool acceptLetter) : LineEdit(parent) { - setFixedWidth(54); - m_intValidator = new QIntValidator(this); - setValue(value); - m_intValidator->setRange(1, 9999); + setFixedWidth(60); + if (acceptLetter) + m_regexpValidator = + new QRegExpValidator(QRegExp("^\\d{1,4}[A-Za-z]?$"), this); + else + m_regexpValidator = new QRegExpValidator(QRegExp("^\\d{1,4}$"), this); - QRegExp rx("^[0-9]{1,4}[A-Ia-i]?$"); - m_regexpValidator = new QRegExpValidator(rx, this); + m_regexpValidator_alt = + new QRegExpValidator(QRegExp("^\\d{1,3}[A-Ia-i]?$"), this); updateValidator(); + + setValue(fId); } //----------------------------------------------------------------------------- void FrameNumberLineEdit::updateValidator() { if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) - setValidator(m_regexpValidator); + setValidator(m_regexpValidator_alt); else - setValidator(m_intValidator); + setValidator(m_regexpValidator); } //----------------------------------------------------------------------------- -void FrameNumberLineEdit::setValue(int value) { - if (value <= 0) - value = 1; - else if (value > 9999) - value = 9999; - +void FrameNumberLineEdit::setValue(TFrameId fId) { QString str; if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { - str = convertToFrameWithLetter(value, 3); + if (fId.getLetter() != '\0') { + // need some warning? + } + str = convertToFrameWithLetter(fId.getNumber(), 3); } else { - str.setNum(value); - while (str.length() < 4) str.push_front("0"); + str = QString::fromStdString(fId.expand()); } setText(str); setCursorPosition(0); @@ -709,18 +711,27 @@ void FrameNumberLineEdit::setValue(int value) { //----------------------------------------------------------------------------- -int FrameNumberLineEdit::getValue() { +TFrameId FrameNumberLineEdit::getValue() { if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { QString str = text(); + int f; // if no letters added if (str.at(str.size() - 1).isDigit()) - return str.toInt() * 10; + f = str.toInt() * 10; else { - return str.left(str.size() - 1).toInt() * 10 + - letterToNum(str.at(str.size() - 1)); + f = str.left(str.size() - 1).toInt() * 10 + + letterToNum(str.at(str.size() - 1)); } - } else - return text().toInt(); + return TFrameId(f); + } else { + QRegExp rx("^(\\d{1,4})([A-Za-z]?)$"); + int pos = rx.indexIn(text()); + if (pos < 0) return TFrameId(); + if (rx.cap(2).isEmpty()) + return TFrameId(rx.cap(1).toInt()); + else + return TFrameId(rx.cap(1).toInt(), rx.cap(2).at(0).toLatin1()); + } } //----------------------------------------------------------------------------- @@ -3578,7 +3589,7 @@ void StopMotionController::onFileTypeActivated() { //----------------------------------------------------------------------------- void StopMotionController::onFrameNumberChanged() { - m_stopMotion->setFrameNumber(m_frameNumberEdit->getValue()); + m_stopMotion->setFrameNumber(m_frameNumberEdit->getValue().getNumber()); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/stopmotion/stopmotioncontroller.h b/toonz/sources/stopmotion/stopmotioncontroller.h index 03c77c27..ba65a92f 100644 --- a/toonz/sources/stopmotion/stopmotioncontroller.h +++ b/toonz/sources/stopmotion/stopmotioncontroller.h @@ -8,6 +8,8 @@ #include "toonzqt/dvdialog.h" #include "toonzqt/lineedit.h" +#include "tfilepath.h" + // TnzQt includes #include "toonzqt/tabbar.h" #include "toonzqt/gutil.h" @@ -72,20 +74,20 @@ class IntLineEdit; class FrameNumberLineEdit : public DVGui::LineEdit { Q_OBJECT /* having two validators and switch them according to the preferences*/ - QIntValidator *m_intValidator; - QRegExpValidator *m_regexpValidator; + QRegExpValidator *m_regexpValidator, *m_regexpValidator_alt; void updateValidator(); QString m_textOnFocusIn; public: - FrameNumberLineEdit(QWidget *parent = 0, int value = 1); + FrameNumberLineEdit(QWidget* parent = 0, TFrameId fId = TFrameId(1), + bool acceptLetter = true); ~FrameNumberLineEdit() {} /*! Set text in field to \b value. */ - void setValue(int value); + void setValue(TFrameId fId); /*! Return an integer with text field value. */ - int getValue(); + TFrameId getValue(); protected: /*! If focus is lost and current text value is out of range emit signal diff --git a/toonz/sources/toonz/autoinputcellnumberpopup.cpp b/toonz/sources/toonz/autoinputcellnumberpopup.cpp index a31d34a9..445a0254 100644 --- a/toonz/sources/toonz/autoinputcellnumberpopup.cpp +++ b/toonz/sources/toonz/autoinputcellnumberpopup.cpp @@ -196,9 +196,9 @@ AutoInputCellNumberPopup::AutoInputCellNumberPopup() "AutoInputCellNumberPopup") { setWindowTitle(tr("Auto Input Cell Number")); - m_from = new FrameNumberLineEdit(this); + m_from = new FrameNumberLineEdit(this, TFrameId(1), false); m_increment = new DVGui::IntLineEdit(this, 0, 0); - m_to = new FrameNumberLineEdit(this); + m_to = new FrameNumberLineEdit(this, TFrameId(1), false); m_interval = new DVGui::IntLineEdit(this, 0, 0); m_step = new DVGui::IntLineEdit(this, 1, 1); m_repeat = new DVGui::IntLineEdit(this, 1, 1); @@ -301,8 +301,8 @@ void AutoInputCellNumberPopup::doExecute(bool overwrite) { } AutoInputCellNumberUndo *undo = new AutoInputCellNumberUndo( m_increment->getValue(), m_interval->getValue(), m_step->getValue(), - m_repeat->getValue(), m_from->getValue(), m_to->getValue(), r0, r1, - overwrite, columnIndices, levels); + m_repeat->getValue(), m_from->getValue().getNumber(), + m_to->getValue().getNumber(), r0, r1, overwrite, columnIndices, levels); // if no cells will be arranged, then return if (undo->rowsCount() == 0) { DVGui::MsgBox(DVGui::WARNING, diff --git a/toonz/sources/toonzlib/filepathproperties.cpp b/toonz/sources/toonzlib/filepathproperties.cpp new file mode 100644 index 00000000..3b79a1a2 --- /dev/null +++ b/toonz/sources/toonzlib/filepathproperties.cpp @@ -0,0 +1,38 @@ +#include "toonz/filepathproperties.h" + +// TnzCore includes +#include "tstream.h" + +FilePathProperties::FilePathProperties() + : m_useStandard(true) + , m_acceptNonAlphabetSuffix(false) + , m_letterCountForSuffix(1) {} + +bool FilePathProperties::isDefault() { + return (m_useStandard == true && m_acceptNonAlphabetSuffix == false && + m_letterCountForSuffix == 1); +} + +void FilePathProperties::saveData(TOStream& os) const { + os.child("useStandard") << ((m_useStandard) ? 1 : 0); + os.child("acceptNonAlphabetSuffix") << ((m_acceptNonAlphabetSuffix) ? 1 : 0); + os.child("letterCountForSuffix") << m_letterCountForSuffix; +} + +// make sure to let TFilePath to know the new properties! +void FilePathProperties::loadData(TIStream& is) { + int val; + std::string tagName; + while (is.matchTag(tagName)) { + if (tagName == "useStandard") { + is >> val; + m_useStandard = (val == 1); + } else if (tagName == "acceptNonAlphabetSuffix") { + is >> val; + m_acceptNonAlphabetSuffix = (val == 1); + } else if (tagName == "letterCountForSuffix") { + is >> m_letterCountForSuffix; + } + is.closeChild(); + } +} \ No newline at end of file From 832c2a62d576ba566f588ef72a28cfc31a656a97 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Thu, 2 Dec 2021 15:53:39 +0900 Subject: [PATCH 04/18] fix fxid and expression handling --- toonz/sources/toonz/columncommand.cpp | 43 +++++++++++++++++-- .../toonz/expressionreferencemanager.cpp | 18 +++++++- .../toonz/expressionreferencemanager.h | 1 + toonz/sources/toonz/subscenecommand.cpp | 9 ++-- toonz/sources/toonzlib/fxcommand.cpp | 3 ++ toonz/sources/toonzqt/stageobjectsdata.cpp | 28 +++++++++--- 6 files changed, 88 insertions(+), 14 deletions(-) diff --git a/toonz/sources/toonz/columncommand.cpp b/toonz/sources/toonz/columncommand.cpp index 8ad68704..dfa39f0a 100644 --- a/toonz/sources/toonz/columncommand.cpp +++ b/toonz/sources/toonz/columncommand.cpp @@ -45,6 +45,8 @@ // TnzBase includes #include "tfx.h" #include "tfxattributes.h" +#include "tparamcontainer.h" +#include "tparamset.h" // TnzCore includes #include "tstroke.h" @@ -100,9 +102,24 @@ void getColumnLinkedFxs(TFx *startFx, TFx *newStartFx, } } +//------------------------------------------------------ + +template +void setGrammerToParams(const ParamCont *cont, + const TSyntax::Grammar *grammer) { + for (int p = 0; p != cont->getParamCount(); ++p) { + TParam ¶m = *cont->getParam(p); + if (TDoubleParam *dp = dynamic_cast(¶m)) + dp->setGrammar(grammer); + else if (TParamSet *paramSet = dynamic_cast(¶m)) + setGrammerToParams(paramSet, grammer); + } +} + //----------------------------------------------------------------------------- -void cloneNotColumnLinkedFxsAndOutputsFx(TXsheet *xsh, TXsheet *newXsh) { +void cloneNotColumnLinkedFxsAndOutputsFx( + TXsheet *xsh, TXsheet *newXsh, QMap *fxTable = nullptr) { int columnCount = xsh->getColumnCount(); assert(newXsh->getColumnCount() == columnCount); @@ -136,6 +153,9 @@ void cloneNotColumnLinkedFxsAndOutputsFx(TXsheet *xsh, TXsheet *newXsh) { newFxDag->getInternalFxs()->addFx(newFx); if (fxDag->getTerminalFxs()->containsFx(fx)) newFxDag->getTerminalFxs()->addFx(newFx); + // if the fx has not unique name then let assignUniqueId() set the default + // name + if (newFx->getName() == newFx->getFxId()) newFx->setName(L""); newFxDag->assignUniqueId(newFx); clonedFxs[fx] = newFx; notColumnLinkedClonedFxs.append(newFx); @@ -203,6 +223,17 @@ void cloneNotColumnLinkedFxsAndOutputsFx(TXsheet *xsh, TXsheet *newXsh) { newOutputFx->getInputPort(0)->setFx(newInputFx); } } + + // reset grammers for all parameters of cloned fxs + // or they fails to refer to other parameters via expression + TSyntax::Grammar *grammer = newXsh->getStageObjectTree()->getGrammar(); + QMap::const_iterator it; + for (it = clonedFxs.constBegin(); it != clonedFxs.constEnd(); ++it) { + setGrammerToParams(it.value()->getParams(), grammer); + + // register to the fx table for expression management + if (fxTable) fxTable->insert(it.key(), it.value()); + } } //----------------------------------------------------------------------------- @@ -1077,11 +1108,13 @@ void ColumnCmd::cloneChild(int index) { data->storeColumns(indices, childXsh, 0); data->storeColumnFxs(indices, childXsh, 0); std::list restoredSplineIds; + QMap idTable; + QMap fxTable; data->restoreObjects(indices, restoredSplineIds, newChildXsh, - StageObjectsData::eDoClone); + StageObjectsData::eDoClone, idTable, fxTable); delete data; - cloneNotColumnLinkedFxsAndOutputsFx(childXsh, newChildXsh); + cloneNotColumnLinkedFxsAndOutputsFx(childXsh, newChildXsh, &fxTable); cloneXsheetTStageObjectTree(childXsh, newChildXsh); /*--以下は、Clone SubXsheet するときに、SubXsheet内にある子SubXsheetをクローンする関数 @@ -1094,6 +1127,10 @@ void ColumnCmd::cloneChild(int index) { newChildXsh->getFxDag()->getXsheetFx()->getAttributes()->setDagNodePos( childXsh->getFxDag()->getXsheetFx()->getAttributes()->getDagNodePos()); + ExpressionReferenceManager::instance()->refreshXsheetRefInfo(newChildXsh); + ExpressionReferenceManager::instance()->transferReference( + childXsh, newChildXsh, idTable, fxTable); + newChildXsh->updateFrameCount(); /*-- TXshChildLevel作成時にsetCellした1つ目のセルを消去 --*/ diff --git a/toonz/sources/toonz/expressionreferencemanager.cpp b/toonz/sources/toonz/expressionreferencemanager.cpp index a43aed35..6cc3d397 100644 --- a/toonz/sources/toonz/expressionreferencemanager.cpp +++ b/toonz/sources/toonz/expressionreferencemanager.cpp @@ -310,6 +310,21 @@ void ExpressionReferenceManager::onSceneSwitched() { //----------------------------------------------------------------------------- +void ExpressionReferenceManager::refreshXsheetRefInfo(TXsheet* xsh) { + xsh->setObserver(this); + m_model->refreshData(xsh); + xsh->getExpRefMonitor()->clearAll(); + for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) { + checkRef(m_model->getStageObjectChannel(i), xsh); + } + for (int i = 0; i < m_model->getFxsChannelCount(); i++) { + checkRef(m_model->getFxChannel(i), xsh); + } + onXsheetSwitched(); +} + +//----------------------------------------------------------------------------- + void ExpressionReferenceManager::onXsheetSwitched() { TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); xsh->setObserver(this); @@ -772,7 +787,8 @@ void ExpressionReferenceManager::transferReference( // 1. create 3 tables for replacing; column indices, parameter pointers, and // expression texts. Note that moving columns in the same xsheet does not need - // to replace the parameter pointers since they are swapped along with columns. + // to replace the parameter pointers since they are swapped along with + // columns. QMap colIdReplaceTable; QMap curveReplaceTable; std::map exprReplaceTable; diff --git a/toonz/sources/toonz/expressionreferencemanager.h b/toonz/sources/toonz/expressionreferencemanager.h index 4f049249..a6a89532 100644 --- a/toonz/sources/toonz/expressionreferencemanager.h +++ b/toonz/sources/toonz/expressionreferencemanager.h @@ -79,6 +79,7 @@ public: bool askIfParamIsIgnoredOnSave(bool saveSubXsheet); + void refreshXsheetRefInfo(TXsheet* xsh); protected slots: void onSceneSwitched(); void onXsheetSwitched(); diff --git a/toonz/sources/toonz/subscenecommand.cpp b/toonz/sources/toonz/subscenecommand.cpp index bb125663..d8344582 100644 --- a/toonz/sources/toonz/subscenecommand.cpp +++ b/toonz/sources/toonz/subscenecommand.cpp @@ -1297,7 +1297,7 @@ void collapseColumns(std::set indices, bool columnsOnly) { data->storeColumns(indices, xsh, StageObjectsData::eDoClone); data->storeColumnFxs(indices, xsh, StageObjectsData::eDoClone); - ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); + // ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); ToonzScene *scene = app->getCurrentScene()->getScene(); TXshLevel *xl = scene->createNewLevel(CHILD_XSHLEVEL); @@ -1320,6 +1320,7 @@ void collapseColumns(std::set indices, bool columnsOnly) { if (!columnsOnly) bringPegbarsInsideChildXsheet(xsh, childXsh, indices, newIndices, idTable); + ExpressionReferenceManager::instance()->refreshXsheetRefInfo(childXsh); ExpressionReferenceManager::instance()->transferReference(xsh, childXsh, idTable, fxTable); @@ -1408,7 +1409,7 @@ void collapseColumns(std::set indices, StageObjectsData::eDoClone); data->storeColumnFxs(indices, xsh, StageObjectsData::eDoClone); - ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); + // ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); ToonzScene *scene = app->getCurrentScene()->getScene(); TXshLevel *xl = scene->createNewLevel(CHILD_XSHLEVEL); @@ -1427,6 +1428,7 @@ void collapseColumns(std::set indices, fxTable); childXsh->updateFrameCount(); + ExpressionReferenceManager::instance()->refreshXsheetRefInfo(childXsh); ExpressionReferenceManager::instance()->transferReference(xsh, childXsh, idTable, fxTable); @@ -1472,7 +1474,7 @@ void collapseColumns(std::set indices, const std::set &fxs, data->storeColumns(indices, xsh, StageObjectsData::eDoClone); data->storeFxs(fxs, xsh, StageObjectsData::eDoClone); - ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); + // ExpressionReferenceMonitor *monitor = xsh->getExpRefMonitor()->clone(); ToonzScene *scene = app->getCurrentScene()->getScene(); TXshLevel *xl = scene->createNewLevel(CHILD_XSHLEVEL); @@ -1491,6 +1493,7 @@ void collapseColumns(std::set indices, const std::set &fxs, if (!columnsOnly) bringPegbarsInsideChildXsheet(xsh, childXsh, indices, newIndices, idTable); + ExpressionReferenceManager::instance()->refreshXsheetRefInfo(childXsh); ExpressionReferenceManager::instance()->transferReference(xsh, childXsh, idTable, fxTable); diff --git a/toonz/sources/toonzlib/fxcommand.cpp b/toonz/sources/toonzlib/fxcommand.cpp index 6b469cd8..59123022 100644 --- a/toonz/sources/toonzlib/fxcommand.cpp +++ b/toonz/sources/toonzlib/fxcommand.cpp @@ -129,6 +129,9 @@ inline void setFxParamToCurrentScene(TFx *fx, TXsheet *xsh) { void initializeFx(TXsheet *xsh, TFx *fx) { if (TZeraryColumnFx *zcfx = dynamic_cast(fx)) fx = zcfx->getZeraryFx(); + // if the fx has not unique name then let assignUniqueId() set the default + // name + if (fx->getName() != L"" && fx->getName() == fx->getFxId()) fx->setName(L""); xsh->getFxDag()->assignUniqueId(fx); setFxParamToCurrentScene(fx, xsh); diff --git a/toonz/sources/toonzqt/stageobjectsdata.cpp b/toonz/sources/toonzqt/stageobjectsdata.cpp index ebe97e00..1938057e 100644 --- a/toonz/sources/toonzqt/stageobjectsdata.cpp +++ b/toonz/sources/toonzqt/stageobjectsdata.cpp @@ -990,10 +990,20 @@ std::vector StageObjectsData::restoreObjects( dynamic_cast(pastedColumn)) { TZeraryColumnFx *zfx = zc->getZeraryColumnFx(); TFx *zeraryFx = zfx->getZeraryFx(); - if (zeraryFx && doClone) { - std::wstring app = zeraryFx->getName(); - fxDag->assignUniqueId(zeraryFx); - zeraryFx->setName(app); + if (zeraryFx) { + if (doClone) { + // if the fx has not unique name then let assignUniqueId() set the + // default name + if (zeraryFx->getName() == zeraryFx->getFxId()) + zeraryFx->setName(L""); + fxDag->assignUniqueId(zeraryFx); + } else + fxDag->updateFxIdTable(zeraryFx); + if (TXshZeraryFxColumn *orig_zc = + dynamic_cast(column)) { + if (TFx *origZeraryFx = orig_zc->getZeraryColumnFx()->getZeraryFx()) + fxTable[origZeraryFx] = zeraryFx; + } } } } @@ -1038,8 +1048,9 @@ std::vector StageObjectsData::restoreObjects( if (doClone) { fx = fxOrig->clone(false); - - fx->setName(fxOrig->getName()); + // if the fx has not unique name then let assignUniqueId() set the default + // name + if (fx->getName() == fx->getFxId()) fx->setName(L""); fx->getAttributes()->setId(fxOrig->getAttributes()->getId()); fx->getAttributes()->passiveCacheDataIdx() = -1; @@ -1084,7 +1095,10 @@ std::vector StageObjectsData::restoreObjects( linkedFx = fx->clone(false); linkedFx->linkParams(fx); - linkedFx->setName(oldLinkedFx->getName()); + // if the fx has not unique name then let assignUniqueId() set the + // default name + if (linkedFx->getName() == linkedFx->getFxId()) + linkedFx->setName(L""); linkedFx->getAttributes()->setId( oldLinkedFx->getAttributes()->getId()); linkedFx->getAttributes()->passiveCacheDataIdx() = -1; From d0e08fac5ca24646e9789905f4b4d8532f3adab9 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 27 Sep 2021 11:17:07 +0900 Subject: [PATCH 05/18] file path processing using regexp --- toonz/sources/common/tcontenthistory.cpp | 4 +- toonz/sources/common/timage_io/tlevel_io.cpp | 5 +- toonz/sources/common/tsystem/tfilepath.cpp | 283 ++++++++++++++++-- toonz/sources/image/avi/tiio_avi.cpp | 4 +- toonz/sources/image/ffmpeg/tiio_gif.cpp | 4 +- toonz/sources/image/ffmpeg/tiio_mov.cpp | 4 +- toonz/sources/image/ffmpeg/tiio_mp4.cpp | 4 +- toonz/sources/image/ffmpeg/tiio_webm.cpp | 4 +- toonz/sources/image/pli/pli_io.cpp | 181 +++++++---- toonz/sources/image/pli/tiio_pli.cpp | 27 +- toonz/sources/image/sprite/tiio_sprite.cpp | 8 +- toonz/sources/image/tzl/tiio_tzl.cpp | 125 +++++--- toonz/sources/include/tfilepath.h | 61 +++- toonz/sources/include/toonz/tproject.h | 5 + toonz/sources/include/toonz/txsheet.h | 5 +- toonz/sources/stopmotion/stopmotion.cpp | 15 +- .../stopmotion/stopmotioncontroller.cpp | 29 +- .../sources/stopmotion/stopmotioncontroller.h | 8 +- toonz/sources/tnztools/tool.cpp | 16 +- toonz/sources/tnztools/toolutils.cpp | 26 +- toonz/sources/toonz/cellselection.cpp | 14 +- toonz/sources/toonz/exportxsheetpdf.cpp | 3 +- toonz/sources/toonz/filebrowserpopup.cpp | 143 ++++----- toonz/sources/toonz/filebrowserpopup.h | 5 +- toonz/sources/toonz/filmstrip.cpp | 6 +- toonz/sources/toonz/filmstripcommand.cpp | 29 +- toonz/sources/toonz/iocommand.cpp | 21 +- toonz/sources/toonz/iocommand.h | 2 +- toonz/sources/toonz/projectpopup.cpp | 217 ++++++++++---- toonz/sources/toonz/projectpopup.h | 14 +- toonz/sources/toonz/tvpjson_io.cpp | 9 +- toonz/sources/toonz/xdtsio.cpp | 20 +- toonz/sources/toonz/xshcellviewer.cpp | 71 +++-- toonz/sources/toonz/xsheetdragtool.cpp | 2 +- toonz/sources/toonzlib/CMakeLists.txt | 2 + .../sources/toonzlib/scriptbinding_scene.cpp | 4 +- toonz/sources/toonzlib/tproject.cpp | 29 +- toonz/sources/toonzlib/txsheet.cpp | 16 +- toonz/sources/toonzlib/txshlevelcolumn.cpp | 41 ++- toonz/sources/toonzlib/txshmeshcolumn.cpp | 44 +-- toonz/sources/toonzlib/txshsimplelevel.cpp | 7 +- 41 files changed, 1042 insertions(+), 475 deletions(-) diff --git a/toonz/sources/common/tcontenthistory.cpp b/toonz/sources/common/tcontenthistory.cpp index ded790d4..b3ad752f 100644 --- a/toonz/sources/common/tcontenthistory.cpp +++ b/toonz/sources/common/tcontenthistory.cpp @@ -73,7 +73,7 @@ inline QString blanks(const QString &str, int count = 15) { //-------------------------------------------------------------------- inline QString getStr(const TFrameId &id) { - if (id.getLetter() != 0) + if (!id.getLetter().isEmpty()) return QString::number(id.getNumber()) + id.getLetter(); else return QString::number(id.getNumber()); @@ -159,7 +159,7 @@ const QString TContentHistory::currentToString() const { dateSorted.insert(pair(it->second, it->first)); std::multimap::const_iterator it1 = dateSorted.begin(); - QDateTime currDate = it1->first; + QDateTime currDate = it1->first; while (it1 != dateSorted.end()) { set frames; diff --git a/toonz/sources/common/timage_io/tlevel_io.cpp b/toonz/sources/common/timage_io/tlevel_io.cpp index 0a628260..7877d339 100644 --- a/toonz/sources/common/timage_io/tlevel_io.cpp +++ b/toonz/sources/common/timage_io/tlevel_io.cpp @@ -132,6 +132,9 @@ TLevelP TLevelReader::loadInfo() { if (!data.empty()) { std::vector::iterator it = std::min_element(data.begin(), data.end(), myLess); + + m_frameFormat = (*it).getFrame().getCurrentFormat(); + /* TFilePath fr = (*it).withoutParentDir().withName("").withType(""); wstring ws = fr.getWideString(); if (ws.length() == 5) { @@ -150,7 +153,7 @@ TLevelP TLevelReader::loadInfo() { else m_frameFormat = TFrameId::UNDERSCORE_NO_PAD; } - + */ } else m_frameFormat = TFrameId::FOUR_ZEROS; diff --git a/toonz/sources/common/tsystem/tfilepath.cpp b/toonz/sources/common/tsystem/tfilepath.cpp index bc33659b..00e01e2b 100644 --- a/toonz/sources/common/tsystem/tfilepath.cpp +++ b/toonz/sources/common/tsystem/tfilepath.cpp @@ -27,9 +27,16 @@ const char wauxslash = '\\'; // QT #include +#include bool TFilePath::m_underscoreFormatAllowed = true; +// specifies file path condition for sequential image for each project. +// See filepathproperties.h +bool TFilePath::m_useStandard = true; +bool TFilePath::m_acceptNonAlphabetSuffix = false; +int TFilePath::m_letterCountForSuffix = 1; + namespace { /*-- fromSeg位置 と @@ -102,23 +109,27 @@ std::string TFrameId::expand(FrameFormat format) const { } else { o_buff << m_frame; } - if (m_letter != '\0') o_buff << m_letter; - return o_buff.str(); + if (m_letter.isEmpty()) + return o_buff.str(); + else + return o_buff.str() + m_letter.toStdString(); } //------------------------------------------------------------------- const TFrameId &TFrameId::operator++() { ++m_frame; - m_letter = 0; + m_letter = ""; + // m_letter = 0; return *this; } //------------------------------------------------------------------- const TFrameId &TFrameId::operator--() { - if (m_letter > 0) - m_letter = 0; + if (!m_letter.isEmpty()) m_letter = ""; + // if (m_letter > 0) + // m_letter = 0; else --m_frame; return *this; @@ -245,9 +256,9 @@ void TFilePath::setPath(std::wstring path) { } // se si tratta di un path in formato UNC e' del tipo "\\\\MachineName" else if ((path.length() >= 3 && path[0] == L'\\' && path[1] == L'\\' && - iswalnum(path[2])) || + iswalnum(path[2])) || (path.length() >= 3 && path[0] == L'/' && path[1] == L'/' && - iswalnum(path[2]))) { + iswalnum(path[2]))) { isUncName = true; m_path.append(2, L'\\'); m_path.append(1, path[2]); @@ -283,14 +294,13 @@ void TFilePath::setPath(std::wstring path) { // oppure sia UNC (Windows only) ) if (!((m_path.length() == 1 && m_path[0] == wslash) || (m_path.length() == 3 && iswalpha(m_path[0]) && m_path[1] == L':' && - m_path[2] == wslash)) && + m_path[2] == wslash)) && (m_path.length() > 1 && m_path[m_path.length() - 1] == wslash)) m_path.erase(m_path.length() - 1, 1); - if (isUncName && - !(m_path.find_last_of(L'\\') > 1 || - m_path.find_last_of(L'/') > - 1)) // e' indicato solo il nome della macchina... + if (isUncName && !(m_path.find_last_of(L'\\') > 1 || + m_path.find_last_of(L'/') > + 1)) // e' indicato solo il nome della macchina... m_path.append(1, wslash); } @@ -520,10 +530,10 @@ bool TFilePath::isAbsolute() const { bool TFilePath::isRoot() const { return ((m_path.length() == 1 && m_path[0] == slash) || (m_path.length() == 3 && iswalpha(m_path[0]) && m_path[1] == ':' && - m_path[2] == slash) || + m_path[2] == slash) || ((m_path.length() > 2 && m_path[0] == slash && m_path[1] == slash) && - (std::string::npos == m_path.find(slash, 2) || - m_path.find(slash, 2) == (m_path.size() - 1)))); + (std::string::npos == m_path.find(slash, 2) || + m_path.find(slash, 2) == (m_path.size() - 1)))); } //----------------------------------------------------------------------------- @@ -531,6 +541,15 @@ bool TFilePath::isRoot() const { // ritorna ""(niente tipo, niente punto), "." (file con tipo) o ".." (file con // tipo e frame) std::string TFilePath::getDots() const { + if (!TFilePath::m_useStandard) { + TFilePathInfo info = analyzePath(); + if (info.extension.isEmpty()) return ""; + if (info.sepChar.isNull()) return "."; + // return ".." regardless of sepChar type (either "_" or ".") + return ".."; + } + //----- + QString type = QString::fromStdString(getType()).toLower(); if (isFfmpegType()) return "."; int i = getLastSlash(m_path); @@ -555,6 +574,12 @@ std::string TFilePath::getDots() const { std::string TFilePath::getDottedType() const // ritorna l'estensione con PUNTO (se c'e') { + if (!TFilePath::m_useStandard) { + QString ext = analyzePath().extension; + if (ext.isEmpty()) return ""; + return "." + ext.toLower().toStdString(); + } + int i = getLastSlash(m_path); std::wstring str = m_path.substr(i + 1); i = str.rfind(L"."); @@ -568,6 +593,15 @@ std::string TFilePath::getDottedType() std::string TFilePath::getUndottedType() const // ritorna l'estensione senza PUNTO { + if (!TFilePath::m_useStandard) { + QString ext = analyzePath().extension; + if (ext.isEmpty()) + return ""; + else + return ext.toLower().toStdString(); + } + + //----- size_t i = getLastSlash(m_path); std::wstring str = m_path.substr(i + 1); i = str.rfind(L"."); @@ -579,6 +613,11 @@ std::string TFilePath::getUndottedType() std::wstring TFilePath::getWideName() const // noDot! noSlash! { + if (!TFilePath::m_useStandard) { + return analyzePath().levelName.toStdWString(); + } + //----- + QString type = QString::fromStdString(getType()).toLower(); int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); @@ -613,6 +652,16 @@ std::string TFilePath::getLevelName() const { // es. TFilePath("/pippo/pluto.0001.gif").getLevelName() == "pluto..gif" std::wstring TFilePath::getLevelNameW() const { + if (!TFilePath::m_useStandard) { + TFilePathInfo info = analyzePath(); + if (info.extension.isEmpty()) return info.levelName.toStdWString(); + QString name = info.levelName; + if (!info.sepChar.isNull()) name += info.sepChar; + name += "." + info.extension; + return name.toStdWString(); + } + //----- + int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' m_path senza directory QString type = QString::fromStdString(getType()).toLower(); @@ -627,7 +676,8 @@ std::wstring TFilePath::getLevelNameW() const { return str; if (!checkForSeqNum(type) || !isNumbers(str, i, j) || - i == (int)std::wstring::npos) return str; + i == (int)std::wstring::npos) + return str; // prova.0001.tif return str.erase(i + 1, j - i - 1); } @@ -638,8 +688,9 @@ TFilePath TFilePath::getParentDir() const // noSlash! { int i = getLastSlash(m_path); // cerco l'ultimo slash if (i < 0) { - if (m_path.length() >= 2 && (('a' <= m_path[0] && m_path[0] <= 'z') || - ('A' <= m_path[0] && m_path[0] <= 'Z')) && + if (m_path.length() >= 2 && + (('a' <= m_path[0] && m_path[0] <= 'z') || + ('A' <= m_path[0] && m_path[0] <= 'Z')) && m_path[1] == ':') return TFilePath(m_path.substr(0, 2)); else @@ -651,8 +702,13 @@ TFilePath TFilePath::getParentDir() const // noSlash! } //----------------------------------------------------------------------------- - +// return true if the fID is EMPTY_FRAME bool TFilePath::isLevelName() const { + if (!TFilePath::m_useStandard) { + return analyzePath().fId.getNumber() == TFrameId::EMPTY_FRAME; + } + //----- + QString type = QString::fromStdString(getType()).toLower(); if (isFfmpegType() || !checkForSeqNum(type)) return false; try { @@ -665,6 +721,11 @@ bool TFilePath::isLevelName() const { } TFrameId TFilePath::getFrame() const { + if (!TFilePath::m_useStandard) { + return analyzePath().fId; + } + + //----- int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir QString type = QString::fromStdString(getType()).toLower(); @@ -680,8 +741,7 @@ TFrameId TFilePath::getFrame() const { if (j == (int)std::wstring::npos) return TFrameId(TFrameId::NO_FRAME); if (i == j + 1) return TFrameId(TFrameId::EMPTY_FRAME); - /*-- 間が数字でない場合(ファイル名にまぎれた"_" や "."がある場合)を除外する - * --*/ + // 間が数字でない場合(ファイル名にまぎれた"_" や "."がある場合)を除外する if (!checkForSeqNum(type) || !isNumbers(str, j, i)) return TFrameId(TFrameId::NO_FRAME); @@ -690,14 +750,13 @@ TFrameId TFilePath::getFrame() const { digits++; number = number * 10 + str[k] - L'0'; } - char letter = '\0'; + char letter = '\0'; if (iswalpha(str[k])) letter = str[k++] + ('a' - L'a'); - /* - if (number == 0 || k < i) // || letter!='\0') - throw TMalformedFrameException( - *this, - str + L": " + QObject::tr("Malformed frame name").toStdWString()); - */ + + // if (number == 0 || k < i) // || letter!='\0') + // throw TMalformedFrameException( + // *this, + // str + L": " + QObject::tr("Malformed frame name").toStdWString()); int padding = 0; if (str[j + 1] == '0') padding = digits; @@ -751,6 +810,20 @@ TFilePath TFilePath::withName(const std::string &name) const { //----------------------------------------------------------------------------- TFilePath TFilePath::withName(const std::wstring &name) const { + if (!TFilePath::m_useStandard) { + TFilePathInfo info = analyzePath(); + + QString ret = info.parentDir + QString::fromStdWString(name); + if (info.fId.getNumber() != TFrameId::NO_FRAME) { + QString sepChar = (info.sepChar.isNull()) ? "." : QString(info.sepChar); + ret += sepChar + QString::fromStdString( + info.fId.expand(info.fId.getCurrentFormat())); + } + if (!info.extension.isEmpty()) ret += "." + info.extension; + + return TFilePath(ret); + } + int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir QString type = QString::fromStdString(getType()).toLower(); @@ -790,6 +863,31 @@ TFilePath TFilePath::withParentDir(const TFilePath &dir) const { TFilePath TFilePath::withFrame(const TFrameId &frame, TFrameId::FrameFormat format) const { + if (!TFilePath::m_useStandard) { + TFilePathInfo info = analyzePath(); + // Override format input because it may be wrong. + if (checkForSeqNum(info.extension)) format = frame.getCurrentFormat(); + // override format if the original fid is available + else if (info.fId.getNumber() != TFrameId::NO_FRAME) + format = info.fId.getCurrentFormat(); + + if (info.extension.isEmpty()) { + if (frame.isEmptyFrame() || frame.isNoFrame()) return *this; + + return TFilePath(m_path + L"." + ::to_wstring(frame.expand(format))); + } + if (frame.isNoFrame()) { + return TFilePath(info.parentDir + info.levelName + "." + info.extension); + } + QString sepChar = (info.sepChar.isNull()) ? "." : QString(info.sepChar); + + return TFilePath(info.parentDir + info.levelName + sepChar + + QString::fromStdString(frame.expand(format)) + "." + + info.extension); + } + + //----------------- + const std::wstring dot = L".", dotDot = L".."; int i = getLastSlash(m_path); // cerco l'ultimo slash std::wstring str = m_path.substr(i + 1); // str e' il path senza parentdir @@ -804,6 +902,8 @@ TFilePath TFilePath::withFrame(const TFrameId &frame, format == TFrameId::UNDERSCORE_NO_PAD || format == TFrameId::UNDERSCORE_CUSTOM_PAD)) ch = "_"; + + // no extension case if (j == (int)std::wstring::npos) { if (frame.isEmptyFrame() || frame.isNoFrame()) return *this; @@ -842,7 +942,7 @@ TFilePath TFilePath::withFrame(const TFrameId &frame, (k == j - 1 || (checkForSeqNum(type) && isNumbers(str, k, - j)))) /*-- "_." の並びか、"_[数字]."の並びのとき --*/ + j)))) //-- "_." の並びか、"_[数字]."の並びのとき -- return TFilePath(m_path.substr(0, k + i + 1) + ((frame.isNoFrame()) ? L"" @@ -893,6 +993,16 @@ TFilePath TFilePath::operator-(const TFilePath &fp) const { //----------------------------------------------------------------------------- bool TFilePath::match(const TFilePath &fp) const { + if (!TFilePath::m_useStandard) { + if (getParentDir() != fp.getParentDir()) return false; + + TFilePathInfo info = analyzePath(); + TFilePathInfo info_ext = fp.analyzePath(); + + return (info.levelName == info_ext.levelName && info.fId == info_ext.fId && + info.extension == info_ext.extension); + } + return getParentDir() == fp.getParentDir() && getName() == fp.getName() && getFrame() == fp.getFrame() && getType() == fp.getType(); } @@ -915,3 +1025,118 @@ void TFilePath::split(std::wstring &head, TFilePath &tail) const { head = ancestor.getWideString(); tail = *this - ancestor; } + +//----------------------------------------------------------------------------- + +QString TFilePath::fidRegExpStr() { + if (m_useStandard) return QString("(\\d+)([a-zA-Z]?)"); + QString suffixLetter = (m_acceptNonAlphabetSuffix) + ? "[^\\._ \\\\/:,;*?\"<>|0123456789]" + : "[a-zA-Z]"; + QString countLetter = (m_letterCountForSuffix == 0) + ? "{0,}" + : (QString("{0,%1}").arg(m_letterCountForSuffix)); + return QString("(\\d+)(%1%2)").arg(suffixLetter).arg(countLetter); + // const QString fIdRegExp("(\\d+)([a-zA-Z]?)"); +} + +//----------------------------------------------------------------------------- + +TFilePath::TFilePathInfo TFilePath::analyzePath() const { + assert(!TFilePath::m_useStandard); + + TFilePath::TFilePathInfo info; + + int i = getLastSlash(m_path); + std::wstring str = m_path.substr(i + 1); + + if (i >= 0) info.parentDir = QString::fromStdWString(m_path.substr(0, i + 1)); + + QString fileName = QString::fromStdWString(str); + + // Level Name : letters other than \/:,;*?"<>| + const QString levelNameRegExp("([^\\\\/:,;*?\"<>|]+)"); + // Sep Char : period or underscore + const QString sepCharRegExp("([\\._])"); + // Frame Number and Suffix + QString fIdRegExp = TFilePath::fidRegExpStr(); + + // Extension:letters other than "._" or \/:,;*?"<>| or " "(space) + const QString extensionRegExp("([^\\._ \\\\/:,;*?\"<>|]+)"); + + // ignore frame numbers on non-sequential (i.e. movie) extension case : + // hoge_0001.mp4 + // QRegExp rx_mf("^" + levelNameRegExp + "\\." + extensionRegExp + "$"); + // if (rx_mf.indexIn(levelName) != -1) { + // QString ext = rx_mf.cap(2); + // if (!checkForSeqNum(ext)) { + // info.levelName = rx_mf.cap(1); + // info.sepChar = QChar(); + // info.fId = TFrameId(TFrameId::NO_FRAME, 0, 0); //NO_PADで初期化する + // info.extension = ext; + // return info; + // } + //} + + // hogehoge.0001a.jpg + // empty frame case : hogehoge..jpg + QRegExp rx("^" + levelNameRegExp + sepCharRegExp + "(?:" + fIdRegExp + ")?" + + "\\." + extensionRegExp + "$"); + if (rx.indexIn(fileName) != -1) { + assert(rx.captureCount() == 5); + info.levelName = rx.cap(1); + info.sepChar = rx.cap(2)[0]; + info.extension = rx.cap(5); + // ignore frame numbers on non-sequential (i.e. movie) extension case : + // hoge_0001.mp4 + if (!checkForSeqNum(info.extension)) { + info.levelName = rx.cap(1) + rx.cap(2); + if (!rx.cap(3).isEmpty()) info.levelName += rx.cap(3); + if (!rx.cap(4).isEmpty()) info.levelName += rx.cap(4); + info.sepChar = QChar(); + info.fId = TFrameId(TFrameId::NO_FRAME, 0, 0); // initialize with NO_PAD + } else { + QString numberStr = rx.cap(3); + if (numberStr.isEmpty()) // empty frame case : hogehoge..jpg + info.fId = + TFrameId(TFrameId::EMPTY_FRAME, 0, 4, info.sepChar.toLatin1()); + else { + int number = numberStr.toInt(); + int padding = 0; + if (numberStr[0] == "0") // with padding + padding = numberStr.count(); + QString suffix; + if (!rx.cap(4).isEmpty()) suffix = rx.cap(4); + info.fId = TFrameId(number, suffix, padding, info.sepChar.toLatin1()); + } + } + return info; + } + + // QRegExp rx_ef("^" + levelNameRegExp + sepCharRegExp + "\\." + + // extensionRegExp + "$"); if (rx_ef.indexIn(levelName) != -1) { + // info.levelName = rx_ef.cap(1); + // info.sepChar = rx_ef.cap(2)[0]; + // info.fId = TFrameId(TFrameId::EMPTY_FRAME, 0, 4, info.sepChar.toLatin1()); + // info.extension = rx_ef.cap(3); + // return info; + //} + + // no frame case : hogehoge.jpg + // no level name case : .jpg + QRegExp rx_nf("^(?:" + levelNameRegExp + ")?\\." + extensionRegExp + "$"); + if (rx_nf.indexIn(fileName) != -1) { + if (!rx_nf.cap(1).isEmpty()) info.levelName = rx_nf.cap(1); + info.sepChar = QChar(); + info.fId = TFrameId(TFrameId::NO_FRAME, 0, 0); // initialize with NO_PAD + info.extension = rx_nf.cap(2); + return info; + } + + // no periods + info.levelName = fileName; + info.sepChar = QChar(); + info.fId = TFrameId(TFrameId::NO_FRAME, 0, 0); // initialize with NO_PAD + info.extension = QString(); + return info; +} \ No newline at end of file diff --git a/toonz/sources/image/avi/tiio_avi.cpp b/toonz/sources/image/avi/tiio_avi.cpp index c5b31404..6e40cff1 100644 --- a/toonz/sources/image/avi/tiio_avi.cpp +++ b/toonz/sources/image/avi/tiio_avi.cpp @@ -332,7 +332,7 @@ void TLevelWriterAvi::createBitmap(int lx, int ly) { TImageWriterP TLevelWriterAvi::getFrameWriter(TFrameId fid) { if (IOError != 0) throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber() - 1; TImageWriterAvi *iwa = new TImageWriterAvi(m_path, index, this); return TImageWriterP(iwa); @@ -879,7 +879,7 @@ TLevelP TLevelReaderAvi::loadInfo() { TImageReaderP TLevelReaderAvi::getFrameReader(TFrameId fid) { if (IOError != 0) throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageReaderP(0); + if (!fid.getLetter().isEmpty()) return TImageReaderP(0); int index = fid.getNumber() - 1; TImageReaderAvi *ira = new TImageReaderAvi(m_path, index, this); diff --git a/toonz/sources/image/ffmpeg/tiio_gif.cpp b/toonz/sources/image/ffmpeg/tiio_gif.cpp index 7a6f02f9..d2d5a258 100644 --- a/toonz/sources/image/ffmpeg/tiio_gif.cpp +++ b/toonz/sources/image/ffmpeg/tiio_gif.cpp @@ -119,7 +119,7 @@ TLevelWriterGif::~TLevelWriterGif() { TImageWriterP TLevelWriterGif::getFrameWriter(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildGifExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber(); TImageWriterGif *iwg = new TImageWriterGif(m_path, index, this); return TImageWriterP(iwg); @@ -224,7 +224,7 @@ TLevelP TLevelReaderGif::loadInfo() { TImageReaderP TLevelReaderGif::getFrameReader(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageReaderP(0); + if (!fid.getLetter().isEmpty()) return TImageReaderP(0); int index = fid.getNumber(); TImageReaderGif *irm = new TImageReaderGif(m_path, index, this, m_info); return TImageReaderP(irm); diff --git a/toonz/sources/image/ffmpeg/tiio_mov.cpp b/toonz/sources/image/ffmpeg/tiio_mov.cpp index 3fb3ef8c..7ded78c4 100644 --- a/toonz/sources/image/ffmpeg/tiio_mov.cpp +++ b/toonz/sources/image/ffmpeg/tiio_mov.cpp @@ -104,7 +104,7 @@ TLevelWriterMov::~TLevelWriterMov() { TImageWriterP TLevelWriterMov::getFrameWriter(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildMovExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber(); TImageWriterMov *iwg = new TImageWriterMov(m_path, index, this); return TImageWriterP(iwg); @@ -207,7 +207,7 @@ TLevelP TLevelReaderMov::loadInfo() { TImageReaderP TLevelReaderMov::getFrameReader(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageReaderP(0); + if (!fid.getLetter().isEmpty()) return TImageReaderP(0); int index = fid.getNumber(); TImageReaderMov *irm = new TImageReaderMov(m_path, index, this, m_info); diff --git a/toonz/sources/image/ffmpeg/tiio_mp4.cpp b/toonz/sources/image/ffmpeg/tiio_mp4.cpp index 4c1b1768..55982247 100644 --- a/toonz/sources/image/ffmpeg/tiio_mp4.cpp +++ b/toonz/sources/image/ffmpeg/tiio_mp4.cpp @@ -100,7 +100,7 @@ TLevelWriterMp4::~TLevelWriterMp4() { TImageWriterP TLevelWriterMp4::getFrameWriter(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildMp4ExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber(); TImageWriterMp4 *iwg = new TImageWriterMp4(m_path, index, this); return TImageWriterP(iwg); @@ -203,7 +203,7 @@ TLevelP TLevelReaderMp4::loadInfo() { TImageReaderP TLevelReaderMp4::getFrameReader(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageReaderP(0); + if (!fid.getLetter().isEmpty()) return TImageReaderP(0); int index = fid.getNumber(); TImageReaderMp4 *irm = new TImageReaderMp4(m_path, index, this, m_info); diff --git a/toonz/sources/image/ffmpeg/tiio_webm.cpp b/toonz/sources/image/ffmpeg/tiio_webm.cpp index e54c7adc..06160456 100644 --- a/toonz/sources/image/ffmpeg/tiio_webm.cpp +++ b/toonz/sources/image/ffmpeg/tiio_webm.cpp @@ -101,7 +101,7 @@ TLevelWriterWebm::~TLevelWriterWebm() { TImageWriterP TLevelWriterWebm::getFrameWriter(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildGifExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber(); TImageWriterWebm *iwg = new TImageWriterWebm(m_path, index, this); return TImageWriterP(iwg); @@ -204,7 +204,7 @@ TLevelP TLevelReaderWebm::loadInfo() { TImageReaderP TLevelReaderWebm::getFrameReader(TFrameId fid) { // if (IOError != 0) // throw TImageException(m_path, buildAVIExceptionString(IOError)); - if (fid.getLetter() != 0) return TImageReaderP(0); + if (!fid.getLetter().isEmpty()) return TImageReaderP(0); int index = fid.getNumber(); TImageReaderWebm *irm = new TImageReaderWebm(m_path, index, this, m_info); diff --git a/toonz/sources/image/pli/pli_io.cpp b/toonz/sources/image/pli/pli_io.cpp index 155738da..99c65614 100644 --- a/toonz/sources/image/pli/pli_io.cpp +++ b/toonz/sources/image/pli/pli_io.cpp @@ -29,7 +29,7 @@ typedef TVectorImage::IntersectionBranch IntersectionBranch; TNZ_LITTLE_ENDIAN undefined !! #endif - static const int c_majorVersionNumber = 120; + static const int c_majorVersionNumber = 150; static const int c_minorVersionNumber = 0; /*=====================================================================*/ @@ -58,8 +58,8 @@ inline double doubleFromUlong1(TUINT32 hi, TUINT32 lo) { l[1] = hi; l[0] = lo; #else - l[0] = hi; - l[1] = lo; + l[0] = hi; + l[1] = lo; #endif return *(double *)l; // - 1; @@ -84,8 +84,8 @@ public: #if TNZ_LITTLE_ENDIAN app = n; #else - UCHAR *uc = (UCHAR *)&n; - app = *(uc) | (*(uc + 1)) << 8 | (*(uc + 2)) << 16 | (*(uc + 3)) << 24; + UCHAR *uc = (UCHAR *)&n; + app = *(uc) | (*(uc + 1)) << 8 | (*(uc + 2)) << 16 | (*(uc + 3)) << 24; #endif write((char *)&app, sizeof(TUINT32)); return *this; @@ -669,15 +669,27 @@ void ParsedPliImp::loadInfo(bool readPlt, TPalette *&palette, USHORT frame; m_iChan >> frame; - char letter = 0; - if (m_majorVersionNumber > 6 || - (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) - m_iChan >> letter; + QByteArray suffix; + if (m_majorVersionNumber >= 150) { + TUINT32 suffixLength; + m_iChan >> suffixLength; + if ((int)suffixLength > 0) { + suffix.resize(suffixLength); + m_iChan.read(suffix.data(), suffixLength); + } + } else { + char letter = 0; + if (m_majorVersionNumber > 6 || + (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) + m_iChan >> letter; + if (letter > 0) suffix = QByteArray(&letter, 1); + } - m_frameOffsInFile[TFrameId(frame, letter)] = m_iChan.tellg(); + m_frameOffsInFile[TFrameId(frame, QString::fromUtf8(suffix))] = + m_iChan.tellg(); // m_iChan.seekg(m_tagLength, ios::cur); - m_iChan.seekg(m_tagLength - 2, ios::cur); + if (m_majorVersionNumber < 150) m_iChan.seekg(m_tagLength - 2, ios::cur); } else if (type == PliTag::STYLE_NGOBJ) { m_iChan.seekg(pos, ios::beg); TagElem *tagElem = readTag(); @@ -798,7 +810,7 @@ ImageTag *ParsedPliImp::loadFrame(const TFrameId &frameNumber) { // PliTag *tag; USHORT type = PliTag::IMAGE_BEGIN_GOBJ; USHORT frame; - char letter; + QByteArray suffix; TFrameId frameId; // cerco il frame @@ -813,13 +825,21 @@ ImageTag *ParsedPliImp::loadFrame(const TFrameId &frameNumber) { while ((type = readTagHeader()) != PliTag::END_CNTRL) { if (type == PliTag::IMAGE_BEGIN_GOBJ) { m_iChan >> frame; - if (m_majorVersionNumber > 6 || - (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) - m_iChan >> letter; - else - letter = 0; + if (m_majorVersionNumber >= 150) { + TUINT32 suffixLength; + m_iChan >> suffixLength; + suffix.resize(suffixLength); + m_iChan.read(suffix.data(), suffixLength); + } else { + char letter = 0; + if (m_majorVersionNumber > 6 || + (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) { + m_iChan >> letter; + if (letter > 0) suffix = QByteArray(&letter, 1); + } + } - frameId = TFrameId(frame, letter); + frameId = TFrameId(frame, QString::fromUtf8(suffix)); m_frameOffsInFile[frameId] = m_iChan.tellg(); if (frameId == frameNumber) break; } else @@ -1056,15 +1076,16 @@ inline bool ParsedPliImp::readDynamicData(TINT32 &val, TUINT32 &bufOffs) { case 4: if (m_isIrixEndian) { val = (m_buf[bufOffs + 3] | (m_buf[bufOffs + 2] << 8) | - (m_buf[bufOffs + 1] << 16) | (m_buf[bufOffs] << 24)) & 0x7fffffff; + (m_buf[bufOffs + 1] << 16) | (m_buf[bufOffs] << 24)) & + 0x7fffffff; if (m_buf[bufOffs] & 0x80) { val = -val; isNegative = true; } } else { val = (m_buf[bufOffs] | (m_buf[bufOffs + 1] << 8) | - (m_buf[bufOffs + 2] << 16) | - (m_buf[bufOffs + 3] << 24)) & 0x7fffffff; + (m_buf[bufOffs + 2] << 16) | (m_buf[bufOffs + 3] << 24)) & + 0x7fffffff; if (m_buf[bufOffs + 3] & 0x80) { val = -val; isNegative = true; @@ -1466,7 +1487,7 @@ UINT ParsedPliImp::readRasterData(TRaster32P &r, TUINT32 &bufOffs) { /*=====================================================================*/ inline void getLongValFromFloat(double val, TINT32 &intVal, TUINT32 &decVal) { - intVal = (TINT32)val; + intVal = (TINT32)val; if (val < 0) decVal = (TUINT32)((double)((-val) - (-intVal)) * 65536.0); /*if (intVal<(0x1<<7)) intVal|=(0x1<<7); @@ -1614,11 +1635,25 @@ PliTag *ParsedPliImp::readImageTag() { bufOffs += 2; int headerLength = 2; - char letter = 0; - if (m_majorVersionNumber > 6 || - (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) { - letter = (char)m_buf[bufOffs++]; - ++headerLength; + + QByteArray suffix; + if (m_majorVersionNumber >= 150) { + TUINT32 suffixLength; + readTUINT32Data(suffixLength, bufOffs); + headerLength += 4; + if (suffixLength > 0) { + suffix = QByteArray((char *)m_buf.get() + bufOffs, suffixLength); + bufOffs += suffixLength; + headerLength += suffixLength; + } + } else { + char letter = 0; + if (m_majorVersionNumber > 6 || + (m_majorVersionNumber == 6 && m_minorVersionNumber >= 6)) { + letter = (char)m_buf[bufOffs++]; + ++headerLength; + if (letter > 0) suffix = QByteArray(&letter, 1); + } } TUINT32 numObjects = (m_tagLength - headerLength) / m_currDynamicTypeBytesNum; @@ -1638,7 +1673,8 @@ PliTag *ParsedPliImp::readImageTag() { assert(false); std::unique_ptr tag( - new ImageTag(TFrameId(frame, letter), numObjects, std::move(object))); + new ImageTag(TFrameId(frame, QString::fromUtf8(suffix)), numObjects, + std::move(object))); return tag.release(); } @@ -1655,8 +1691,8 @@ inline double doubleFromUlong(TUINT32 q) { l[1] = 0x3FF00000 | (q >> 12); l[0] = (q & 0xFFE) << 20; #else - l[0] = 0x3FF00000 | (q >> 12); - l[1] = (q & 0xFFE) << 20; + l[0] = 0x3FF00000 | (q >> 12); + l[1] = (q & 0xFFE) << 20; #endif return *(double *)l - 1; @@ -1700,7 +1736,7 @@ PliTag *ParsedPliImp::readIntersectionDataTag() { readUShortData(style, bufOffs); branchArray[i].m_style = style; /* -*/ + */ if (m_buf[bufOffs] & 0x80) // in un numero double tra 0 e 1, il bit piu' // significativo e' sempre 0 // sfrutto questo bit; se e' 1, vuol dire che il valore e' 0.0 o 1.0 in un @@ -1981,7 +2017,7 @@ TUINT32 ParsedPliImp::writePaletteWithAlphaTag(PaletteWithAlphaTag *tag) { inline void ParsedPliImp::WRITE_UCHAR_FROM_DOUBLE(double dval) { assert(m_oChan); - int ival = tround(dval); + int ival = tround(dval); if (ival > 255) ival = 255; assert(ival >= 0); *m_oChan << (UCHAR)ival; @@ -2128,10 +2164,24 @@ TUINT32 ParsedPliImp::writeImageTag(ImageTag *tag) { TUINT32 *objectOffset, offset, tagLength; int maxval = 0, minval = 100000; - writeTagHeader((UCHAR)PliTag::IMAGE_BEGIN_GOBJ, 3); - *m_oChan << (USHORT)tag->m_numFrame.getNumber(); - *m_oChan << tag->m_numFrame.getLetter(); - + QByteArray suffix = tag->m_numFrame.getLetter().toUtf8(); + TUINT32 suffixLength = suffix.size(); + UINT fIdTagLength; + if (m_majorVersionNumber >= 150) { // write the suffix length before data + fIdTagLength = 2 + 4 + suffixLength; + writeTagHeader((UCHAR)PliTag::IMAGE_BEGIN_GOBJ, fIdTagLength); + *m_oChan << (USHORT)tag->m_numFrame.getNumber(); + *m_oChan << suffixLength; + if (suffixLength > 0) m_oChan->writeBuf(suffix.data(), suffixLength); + } else { // write only the first byte + fIdTagLength = 3; + writeTagHeader((UCHAR)PliTag::IMAGE_BEGIN_GOBJ, fIdTagLength); + *m_oChan << (USHORT)tag->m_numFrame.getNumber(); + if (suffixLength > 0) + m_oChan->writeBuf(suffix.data(), 1); + else + *m_oChan << (UCHAR)0; + } m_currDynamicTypeBytesNum = 3; objectOffset = new TUINT32[tag->m_numObjects]; @@ -2155,12 +2205,23 @@ TUINT32 ParsedPliImp::writeImageTag(ImageTag *tag) { setDynamicTypeBytesNum(minval, maxval); - tagLength = tag->m_numObjects * m_currDynamicTypeBytesNum + 3; + tagLength = tag->m_numObjects * m_currDynamicTypeBytesNum + fIdTagLength; + // tagLength = tag->m_numObjects * m_currDynamicTypeBytesNum + 3; offset = writeTagHeader((UCHAR)PliTag::IMAGE_GOBJ, tagLength); + suffix = tag->m_numFrame.getLetter().toUtf8(); *m_oChan << (USHORT)tag->m_numFrame.getNumber(); - *m_oChan << tag->m_numFrame.getLetter(); + + if (m_majorVersionNumber >= 150) { // write the suffix length before data + *m_oChan << suffixLength; + if (suffixLength > 0) m_oChan->writeBuf(suffix.data(), suffixLength); + } else { // write only the first byte + if (suffixLength > 0) + m_oChan->writeBuf(suffix.data(), 1); + else + *m_oChan << (UCHAR)0; + } for (i = 0; i < tag->m_numObjects; i++) writeDynamicData(objectOffset[i]); @@ -2420,29 +2481,29 @@ TUINT32 ParsedPliImp::writeGeometricTransformationTag( if (objectOffset > (unsigned int)maxval) maxval = (int)objectOffset; getLongValFromFloat(tag->m_affine.a11, intVal[0], decVal[0]); - if (intVal[0] < minval) minval = (int)intVal[0]; - if (intVal[0] > maxval) maxval = (int)intVal[0]; + if (intVal[0] < minval) minval = (int)intVal[0]; + if (intVal[0] > maxval) maxval = (int)intVal[0]; if (decVal[0] > (unsigned int)maxval) maxval = (int)decVal[0]; getLongValFromFloat(tag->m_affine.a12, intVal[1], decVal[1]); if (decVal[1] > (unsigned int)maxval) maxval = (int)decVal[1]; - if (intVal[1] < minval) minval = (int)intVal[1]; - if (intVal[1] > maxval) maxval = (int)intVal[1]; + if (intVal[1] < minval) minval = (int)intVal[1]; + if (intVal[1] > maxval) maxval = (int)intVal[1]; getLongValFromFloat(tag->m_affine.a13, intVal[2], decVal[2]); if (decVal[2] > (unsigned int)maxval) maxval = (int)decVal[2]; - if (intVal[2] < minval) minval = (int)intVal[2]; - if (intVal[2] > maxval) maxval = (int)intVal[2]; + if (intVal[2] < minval) minval = (int)intVal[2]; + if (intVal[2] > maxval) maxval = (int)intVal[2]; getLongValFromFloat(tag->m_affine.a21, intVal[3], decVal[3]); if (decVal[3] > (unsigned int)maxval) maxval = (int)decVal[3]; - if (intVal[3] < minval) minval = (int)intVal[3]; - if (intVal[3] > maxval) maxval = (int)intVal[3]; + if (intVal[3] < minval) minval = (int)intVal[3]; + if (intVal[3] > maxval) maxval = (int)intVal[3]; getLongValFromFloat(tag->m_affine.a22, intVal[4], decVal[4]); if (decVal[4] > (unsigned int)maxval) maxval = (int)decVal[4]; - if (intVal[4] < minval) minval = (int)intVal[4]; - if (intVal[4] > maxval) maxval = (int)intVal[4]; + if (intVal[4] < minval) minval = (int)intVal[4]; + if (intVal[4] > maxval) maxval = (int)intVal[4]; getLongValFromFloat(tag->m_affine.a23, intVal[5], decVal[5]); if (decVal[5] > (unsigned int)maxval) maxval = (int)decVal[5]; - if (intVal[5] < minval) minval = (int)intVal[5]; - if (intVal[5] > maxval) maxval = (int)intVal[5]; + if (intVal[5] < minval) minval = (int)intVal[5]; + if (intVal[5] > maxval) maxval = (int)intVal[5]; setDynamicTypeBytesNum(minval, maxval); @@ -2479,11 +2540,11 @@ TUINT32 ParsedPliImp::writeDoublePairTag(DoublePairTag *tag) { getLongValFromFloat(tag->m_first, xIntVal, xDecVal); getLongValFromFloat(tag->m_second, yIntVal, yDecVal); - if (xIntVal < minval) minval = (int)xIntVal; - if (xIntVal > maxval) maxval = (int)xIntVal; + if (xIntVal < minval) minval = (int)xIntVal; + if (xIntVal > maxval) maxval = (int)xIntVal; if ((int)xDecVal > maxval) maxval = (int)xDecVal; - if (yIntVal < minval) minval = (int)yIntVal; - if (yIntVal > maxval) maxval = (int)yIntVal; + if (yIntVal < minval) minval = (int)yIntVal; + if (yIntVal > maxval) maxval = (int)yIntVal; if ((int)yDecVal > maxval) maxval = (int)yDecVal; setDynamicTypeBytesNum(minval, maxval); @@ -2635,10 +2696,16 @@ void ParsedPli::getVersion(UINT &majorVersionNumber, /*=====================================================================*/ void ParsedPli::setVersion(UINT majorVersionNumber, UINT minorVersionNumber) { - if (imp->m_versionLocked) return; + if (imp->m_versionLocked) { + // accept only when settings higher versions + if ((imp->m_majorVersionNumber > majorVersionNumber) || + (imp->m_majorVersionNumber == majorVersionNumber && + imp->m_minorVersionNumber >= minorVersionNumber)) + return; + } if (majorVersionNumber >= 120) imp->m_versionLocked = true; - imp->m_majorVersionNumber = majorVersionNumber; - imp->m_minorVersionNumber = minorVersionNumber; + imp->m_majorVersionNumber = majorVersionNumber; + imp->m_minorVersionNumber = minorVersionNumber; } /*=====================================================================*/ diff --git a/toonz/sources/image/pli/tiio_pli.cpp b/toonz/sources/image/pli/tiio_pli.cpp index 311e9820..4a44841b 100644 --- a/toonz/sources/image/pli/tiio_pli.cpp +++ b/toonz/sources/image/pli/tiio_pli.cpp @@ -160,7 +160,7 @@ void buildPalette(ParsedPli *pli, const TImageP img) { assert(vPalette->getPageCount()); std::vector pageNames(vPalette->getPageCount()); - for (i = 0; i < pageNames.size(); i++) + for (i = 0; i < pageNames.size(); i++) pageNames[i] = TStyleParam(::to_string(vPalette->getPage(i)->getName())); StyleTag *pageNamesTag = new StyleTag(0, 0, pageNames.size(), pageNames.data()); @@ -414,18 +414,18 @@ TImageP TImageReaderPli::doLoad() { } // switch(groupTag->m_object[j]->m_type) } // for (i=0; im_numObjects; i++) -//} // try + //} // try -// catch(...) // cosi' e' inutile o raccolgo qualcosa prima di rilanciare o lo -// elimino -//{ -// throw; -// } + // catch(...) // cosi' e' inutile o raccolgo qualcosa prima di rilanciare o lo + // elimino + //{ + // throw; + // } -// if (regionsComputed) //WARNING !!! la seedFill mette il flag a ValidRegion a -// TRUE -// outVectImage->seedFill(); //le vecchie immagini hanno il seed -// (version<3.1) + // if (regionsComputed) //WARNING !!! la seedFill mette il flag a ValidRegion + // a TRUE + // outVectImage->seedFill(); //le vecchie immagini hanno il seed + // (version<3.1) #ifdef _DEBUG outVectImage->checkIntersections(); @@ -564,6 +564,9 @@ solo nel costruttore) PliTag *tag = new PrecisionScaleTag(precisionScale); tags.push_back((PliObjectTag *)tag); } + + // update the format version if multiple suffixes is supported0 + if (!TFilePath::useStandard()) pli->setVersion(150, 0); // Store the auto close tolerance double pliTolerance = m_lwp->m_pli->getAutocloseTolerance(); // write the tag if the frame's tolerance has been changed or @@ -744,7 +747,7 @@ TPalette *readPalette(GroupTag *paletteTag, int majorVersion, // caricarli! std::vector params(styleTag->m_numParams); - for (int j = 0; j < styleTag->m_numParams; j++) + for (int j = 0; j < styleTag->m_numParams; j++) params[j] = styleTag->m_param[j]; PliInputStream chan(¶ms, majorVersion, minorVersion); diff --git a/toonz/sources/image/sprite/tiio_sprite.cpp b/toonz/sources/image/sprite/tiio_sprite.cpp index 1300bdf3..353fc9cc 100644 --- a/toonz/sources/image/sprite/tiio_sprite.cpp +++ b/toonz/sources/image/sprite/tiio_sprite.cpp @@ -190,7 +190,7 @@ TLevelWriterSprite::~TLevelWriterSprite() { //----------------------------------------------------------- TImageWriterP TLevelWriterSprite::getFrameWriter(TFrameId fid) { - if (fid.getLetter() != 0) return TImageWriterP(0); + if (!fid.getLetter().isEmpty()) return TImageWriterP(0); int index = fid.getNumber(); TImageWriterSprite *iwg = new TImageWriterSprite(m_path, index, this); return TImageWriterP(iwg); @@ -265,9 +265,9 @@ void TLevelWriterSprite::save(const TImageP &img, int frameIndex) { m_top = t; m_bottom = b; } else { - if (l < m_left) m_left = l; - if (r > m_right) m_right = r; - if (t < m_top) m_top = t; + if (l < m_left) m_left = l; + if (r > m_right) m_right = r; + if (t < m_top) m_top = t; if (b > m_bottom) m_bottom = b; } QImage *newQi = new QImage(m_lx, m_ly, QImage::Format_ARGB32_Premultiplied); diff --git a/toonz/sources/image/tzl/tiio_tzl.cpp b/toonz/sources/image/tzl/tiio_tzl.cpp index 34c0134e..b944dc78 100644 --- a/toonz/sources/image/tzl/tiio_tzl.cpp +++ b/toonz/sources/image/tzl/tiio_tzl.cpp @@ -25,8 +25,7 @@ TNZ_LITTLE_ENDIAN undefined !! #endif - const int CURRENT_VERSION = 14; -const int CREATOR_LENGTH = 40; + const int CREATOR_LENGTH = 40; namespace { @@ -40,6 +39,13 @@ char *reverse(char *buffer, int size) { } return buffer; } + +// switch the saving version according to the file path property +int currentVersion() { + if (TFilePath::useStandard()) return 14; + return 15; +} + } // namespace static int tfwrite(const char *data, const unsigned int count, FILE *f) { @@ -138,6 +144,8 @@ bool readVersion(FILE *chan, int &version) { version = 13; } else if (memcmp(magic, "TLV14", 5) == 0) { version = 14; + } else if (memcmp(magic, "TLV15", 5) == 0) { + version = 15; } else { return false; } @@ -162,7 +170,7 @@ bool readHeaderAndOffsets(FILE *chan, TzlOffsetMap &frameOffsTable, if (!readVersion(chan, version)) return false; // read creator - if (version == 14) { + if (version >= 14) { char buffer[CREATOR_LENGTH + 1]; memset(buffer, 0, sizeof buffer); fread(&buffer, sizeof(char), CREATOR_LENGTH, chan); @@ -201,9 +209,18 @@ bool readHeaderAndOffsets(FILE *chan, TzlOffsetMap &frameOffsTable, TFrameId oldFid(TFrameId::EMPTY_FRAME); for (int i = 0; i < (int)frameCount; i++) { TINT32 number, offs, length; - char letter; + QByteArray suffix; fread(&number, sizeof(TINT32), 1, chan); - fread(&letter, sizeof(char), 1, chan); + if (version >= 15) { + TINT32 suffixLength; + fread(&suffixLength, sizeof(TINT32), 1, chan); + suffix.resize(suffixLength); + fread(suffix.data(), sizeof(char), suffixLength, chan); + } else { + char letter; + fread(&letter, sizeof(char), 1, chan); + suffix = QByteArray(&letter, 1); + } fread(&offs, sizeof(TINT32), 1, chan); if (version >= 12) fread(&length, sizeof(TINT32), 1, chan); @@ -215,7 +232,7 @@ bool readHeaderAndOffsets(FILE *chan, TzlOffsetMap &frameOffsTable, // std::cout << "#" << i << std::hex << " n 0x" << number //<< " l 0x" << letter << " o 0x" << offs << std::dec << std::endl; - TFrameId fid(number, letter); + TFrameId fid(number, QString::fromUtf8(suffix)); // assert(i==0 || oldFid= 12) { @@ -241,9 +258,18 @@ bool readHeaderAndOffsets(FILE *chan, TzlOffsetMap &frameOffsTable, for (int i = 0; i < (int)frameCount; i++) { TINT32 number, thumbnailOffs, thumbnailLength; - char letter; + QByteArray suffix; fread(&number, sizeof(TINT32), 1, chan); - fread(&letter, sizeof(char), 1, chan); + if (version >= 15) { + TINT32 suffixLength; + fread(&suffixLength, sizeof(TINT32), 1, chan); + suffix.resize(suffixLength); + fread(suffix.data(), sizeof(char), suffixLength, chan); + } else { + char letter; + fread(&letter, sizeof(char), 1, chan); + suffix = QByteArray(&letter, 1); + } fread(&thumbnailOffs, sizeof(TINT32), 1, chan); fread(&thumbnailLength, sizeof(TINT32), 1, chan); @@ -252,7 +278,7 @@ bool readHeaderAndOffsets(FILE *chan, TzlOffsetMap &frameOffsTable, thumbnailOffs = swapTINT32(thumbnailOffs); thumbnailLength = swapTINT32(thumbnailLength); #endif - TFrameId fid(number, letter); + TFrameId fid(number, QString::fromUtf8(suffix)); iconOffsTable[fid] = TzlChunk(thumbnailOffs, thumbnailLength); } } @@ -460,7 +486,7 @@ void TLevelWriterTzl::buildFreeChunksTable() { // dati relativi alle immagini if (m_version == 13) curPos = 6 * sizeof(TINT32) + 4 * sizeof(char) + 8 * sizeof(char); - else if (m_version == 14) + else if (m_version >= 14) curPos = 6 * sizeof(TINT32) + 4 * sizeof(char) + 8 * sizeof(char) + CREATOR_LENGTH * sizeof(char); else @@ -495,7 +521,7 @@ TLevelWriterTzl::TLevelWriterTzl(const TFilePath &path, TPropertyGroup *info) , m_palette(0) , m_res(0, 0) , m_exists(false) - , m_version(CURRENT_VERSION) + , m_version(currentVersion()) , m_updatedIconsSize(false) , m_currentIconSize(0, 0) , m_iconSize(TDimension(80, 60)) @@ -506,13 +532,14 @@ TLevelWriterTzl::TLevelWriterTzl(const TFilePath &path, TPropertyGroup *info) m_path = path; m_palettePath = path.withNoFrame().withType("tpl"); TFileStatus fs(path); - m_magic = "TLV14B1a"; // actual version + m_magic = (m_version == 14) ? "TLV14B1a" : "TLV15B1a"; // actual version erasedFrame = false; // version TLV10B1a: first version // version TLV11B1a: added frameIds // version TLV12B1a: incremental writings // version TLV13B1a: added thumbnails - // version TLV15B1a: add creator string (fixed size = CREATOR_LENGTH char) + // version TLV14B1a: add creator string (fixed size = CREATOR_LENGTH char) + // version TLV15B1a: support multiple suffixes if (fs.doesExist()) { // if (!fs.isWritable()) @@ -530,7 +557,7 @@ TLevelWriterTzl::TLevelWriterTzl(const TFilePath &path, TPropertyGroup *info) if (m_version >= 12) buildFreeChunksTable(); m_headerWritten = true; m_exists = true; - if (m_version == 14) + if (m_version >= 14) m_frameCountPos = 8 + CREATOR_LENGTH + 3 * sizeof(TINT32); else m_frameCountPos = 8 + 3 * sizeof(TINT32); @@ -555,9 +582,9 @@ TLevelWriterTzl::TLevelWriterTzl(const TFilePath &path, TPropertyGroup *info) //------------------------------------------------------------------- TLevelWriterTzl::~TLevelWriterTzl() { - if (m_version < CURRENT_VERSION) { + if (m_version < currentVersion()) { if (!convertToLatestVersion()) return; - assert(m_version == CURRENT_VERSION); + assert(m_version == currentVersion()); } delete m_codec; @@ -573,13 +600,18 @@ TLevelWriterTzl::~TLevelWriterTzl() { TzlOffsetMap::iterator it = m_frameOffsTable.begin(); for (; it != m_frameOffsTable.end(); ++it) { - TFrameId fid = it->first; - TINT32 num = fid.getNumber(); - char letter = fid.getLetter(); - TINT32 offs = it->second.m_offs; - TINT32 length = it->second.m_length; + TFrameId fid = it->first; + TINT32 num = fid.getNumber(); + QByteArray suffix = fid.getLetter().toUtf8(); + TINT32 offs = it->second.m_offs; + TINT32 length = it->second.m_length; tfwrite(&num, 1, m_chan); - tfwrite(&letter, 1, m_chan); + if (m_version >= 15) { // write the suffix length before data + TINT32 suffixLength = suffix.size(); + tfwrite(&suffixLength, 1, m_chan); + tfwrite(suffix.constData(), suffixLength, m_chan); + } else // write only the first byte + tfwrite(suffix.constData(), 1, m_chan); tfwrite(&offs, 1, m_chan); tfwrite(&length, 1, m_chan); } @@ -593,11 +625,16 @@ TLevelWriterTzl::~TLevelWriterTzl() { for (; iconIt != m_iconOffsTable.end(); ++iconIt) { TFrameId fid = iconIt->first; TINT32 num = fid.getNumber(); - char letter = fid.getLetter(); + QByteArray suffix = fid.getLetter().toUtf8(); TINT32 thumbnailOffs = iconIt->second.m_offs; TINT32 thumbnailLength = iconIt->second.m_length; tfwrite(&num, 1, m_chan); - tfwrite(&letter, 1, m_chan); + if (m_version >= 15) { // write the suffix length before data + TINT32 suffixLength = suffix.size(); + tfwrite(&suffixLength, 1, m_chan); + tfwrite(suffix.constData(), suffixLength, m_chan); + } else // write only the first byte + tfwrite(suffix.constData(), 1, m_chan); tfwrite(&thumbnailOffs, 1, m_chan); tfwrite(&thumbnailLength, 1, m_chan); } @@ -774,7 +811,7 @@ bool TLevelWriterTzl::convertToLatestVersion() { if (!m_chan) return false; if (!writeVersionAndCreator(m_chan, m_magic, m_creator)) return false; m_creatorWritten = true; - m_version = CURRENT_VERSION; + m_version = currentVersion(); TLevelReaderP lr(tempPath); if (!lr) return false; TLevelP level = lr->loadInfo(); @@ -798,13 +835,18 @@ bool TLevelWriterTzl::convertToLatestVersion() { TzlOffsetMap::iterator it2 = m_frameOffsTable.begin(); for (; it2 != m_frameOffsTable.end(); ++it2) { - TFrameId fid = it2->first; - TINT32 num = fid.getNumber(); - char letter = fid.getLetter(); - TINT32 offs = it2->second.m_offs; - TINT32 length = it2->second.m_length; + TFrameId fid = it2->first; + TINT32 num = fid.getNumber(); + QByteArray suffix = fid.getLetter().toUtf8(); + TINT32 offs = it2->second.m_offs; + TINT32 length = it2->second.m_length; tfwrite(&num, 1, m_chan); - tfwrite(&letter, 1, m_chan); + if (m_version >= 15) { // write the suffix length before data + TINT32 suffixLength = suffix.size(); + tfwrite(&suffixLength, 1, m_chan); + tfwrite(suffix.constData(), suffixLength, m_chan); + } else // write only the first byte + tfwrite(suffix.constData(), 1, m_chan); tfwrite(&offs, 1, m_chan); tfwrite(&length, 1, m_chan); } @@ -816,11 +858,16 @@ bool TLevelWriterTzl::convertToLatestVersion() { for (; iconIt != m_iconOffsTable.end(); ++iconIt) { TFrameId fid = iconIt->first; TINT32 num = fid.getNumber(); - char letter = fid.getLetter(); + QByteArray suffix = fid.getLetter().toUtf8(); TINT32 thumbnailOffs = iconIt->second.m_offs; TINT32 thumbnailLength = iconIt->second.m_length; tfwrite(&num, 1, m_chan); - tfwrite(&letter, 1, m_chan); + if (m_version >= 15) { // write the suffix length before data + TINT32 suffixLength = suffix.size(); + tfwrite(&suffixLength, 1, m_chan); + tfwrite(suffix.constData(), suffixLength, m_chan); + } else // write only the first byte + tfwrite(suffix.constData(), 1, m_chan); tfwrite(&thumbnailOffs, 1, m_chan); tfwrite(&thumbnailLength, 1, m_chan); } @@ -856,7 +903,7 @@ bool TLevelWriterTzl::convertToLatestVersion() { m_headerWritten = true; m_exists = true; m_frameCountPos = 8 + CREATOR_LENGTH + 3 * sizeof(TINT32); - assert(m_version == CURRENT_VERSION); + assert(m_version == currentVersion()); if (!m_renumberTable.empty()) renumberFids(m_renumberTable); return true; } @@ -867,9 +914,9 @@ void TLevelWriterTzl::saveImage(const TImageP &img, const TFrameId &_fid, if (!m_chan) return; // se il file è di una versione precedente allora lo converto prima - if (m_version < CURRENT_VERSION) { + if (m_version < currentVersion()) { if (!convertToLatestVersion()) return; - assert(m_version == CURRENT_VERSION); + assert(m_version == currentVersion()); } if (!m_updatedIconsSize && m_exists) @@ -1348,7 +1395,7 @@ float TLevelWriterTzl::getFreeSpace() { if (m_version == 13) totalSpace = m_offsetTablePos - 6 * sizeof(TINT32) - 4 * sizeof(char) - 8 * sizeof(char); - else if (m_version == 14) + else if (m_version >= 14) totalSpace = m_offsetTablePos - 6 * sizeof(TINT32) - 4 * sizeof(char) - 8 * sizeof(char) - CREATOR_LENGTH * sizeof(char); assert(totalSpace > 0); @@ -2285,6 +2332,10 @@ TImageP TImageReaderTzl::load() { if (!m_lrp->m_frameOffsTable.empty() && !m_lrp->m_iconOffsTable.empty()) image = load14(); break; + case 15: // same as v14 + if (!m_lrp->m_frameOffsTable.empty() && !m_lrp->m_iconOffsTable.empty()) + image = load14(); + break; default: image = load10(); } diff --git a/toonz/sources/include/tfilepath.h b/toonz/sources/include/tfilepath.h index 3004db6f..72700aec 100644 --- a/toonz/sources/include/tfilepath.h +++ b/toonz/sources/include/tfilepath.h @@ -16,7 +16,7 @@ #define DVVAR DV_IMPORT_VAR #endif -class QString; +#include //----------------------------------------------------------------------------- /* @@ -28,8 +28,8 @@ class QString; //! figures and, in case, by a character (necessary for added frames) class DVAPI TFrameId { int m_frame; - char m_letter; // serve per i frame "aggiunti" del tipo pippo.0001a.tzp => - // f=1 c='a' + QString m_letter; // serve per i frame "aggiunti" del tipo pippo.0001a.tzp => + // f=1 c='a' int m_zeroPadding; char m_startSeqInd; @@ -50,13 +50,13 @@ public: }; // pippo_1.tif TFrameId(int f = EMPTY_FRAME) - : m_frame(f), m_letter(0), m_zeroPadding(4), m_startSeqInd('.') {} - TFrameId(int f, char c) - : m_frame(f), m_letter(c), m_zeroPadding(4), m_startSeqInd('.') {} - TFrameId(int f, char c, int p) - : m_frame(f), m_letter(c), m_zeroPadding(p), m_startSeqInd('.') {} - TFrameId(int f, char c, int p, char s) - : m_frame(f), m_letter(c), m_zeroPadding(p), m_startSeqInd(s) {} + : m_frame(f), m_letter(""), m_zeroPadding(4), m_startSeqInd('.') {} + TFrameId(int f, char c, int p = 4, char s = '.') + : m_frame(f), m_zeroPadding(p), m_startSeqInd(s) { + m_letter = (c == '\0') ? "" : QString(c); + } + TFrameId(int f, QString str, int p = 4, char s = '.') + : m_frame(f), m_letter(str), m_zeroPadding(p), m_startSeqInd(s) {} inline bool operator==(const TFrameId &f) const { return f.m_frame == m_frame && f.m_letter == m_letter; @@ -66,7 +66,8 @@ public: } inline bool operator<(const TFrameId &f) const { return (m_frame < f.m_frame || - (m_frame == f.m_frame && m_letter < f.m_letter)); + (m_frame == f.m_frame && + QString::localeAwareCompare(m_letter, f.m_letter) < 0)); } inline bool operator>(const TFrameId &f) const { return f < *this; } inline bool operator>=(const TFrameId &f) const { return !operator<(f); } @@ -89,7 +90,7 @@ public: // operator string() const; std::string expand(FrameFormat format = FOUR_ZEROS) const; int getNumber() const { return m_frame; } - char getLetter() const { return m_letter; } + QString getLetter() const { return m_letter; } void setZeroPadding(int p) { m_zeroPadding = p; } int getZeroPadding() const { return m_zeroPadding; } @@ -132,7 +133,23 @@ inline std::ostream &operator<<(std::ostream &out, const TFrameId &f) { constructor.*/ class DVAPI TFilePath { static bool m_underscoreFormatAllowed; + + // specifies file path condition for sequential image for each project. + // See filepathproperties.h + static bool m_useStandard; + static bool m_acceptNonAlphabetSuffix; + static int m_letterCountForSuffix; + std::wstring m_path; + + struct TFilePathInfo { + QString parentDir; // with slash + QString levelName; + QChar sepChar; // either "." or "_" + TFrameId fId; + QString extension; + }; + void setPath(std::wstring path); public: @@ -142,6 +159,23 @@ public: m_underscoreFormatAllowed = state; } + // called from TProjectManager::getCurrentProject() and + // ProjectPopup::updateProjectFromFields + // returns true if something changed + static bool setFilePathProperties(bool useStandard, bool acceptNonAlphaSuffix, + int letterCountForSuffix) { + if (m_useStandard == useStandard && + m_acceptNonAlphabetSuffix == acceptNonAlphaSuffix && + m_letterCountForSuffix == letterCountForSuffix) + return false; + m_useStandard = useStandard; + m_acceptNonAlphabetSuffix = acceptNonAlphaSuffix; + m_letterCountForSuffix = letterCountForSuffix; + return true; + } + static bool useStandard() { return m_useStandard; } + static QString fidRegExpStr(); + /*!This constructor creates a string removing redundances ('//', './',etc.) and final slashes, correcting (redressing) the "twisted" slashes. @@ -191,6 +225,7 @@ If the path is ":" a slash will be added*/ std::string getType() const { return getUndottedType(); } // ritorna l'estensione SENZA PUNTO + /*!Returns the base filename (no extension, no dots, no slash)*/ std::string getName() const; // noDot! noSlash! std::wstring getWideName() const; // noDot! noSlash! @@ -274,6 +309,8 @@ type is a string that indicate the filename extension(ex:. bmp or .bmp)*/ // '/a/b/c.txt' => head='a' tail='b/c.txt' void split(std::wstring &head, TFilePath &tail) const; + + TFilePathInfo analyzePath() const; }; //----------------------------------------------------------------------------- diff --git a/toonz/sources/include/toonz/tproject.h b/toonz/sources/include/toonz/tproject.h index c5a6340e..7c76a472 100644 --- a/toonz/sources/include/toonz/tproject.h +++ b/toonz/sources/include/toonz/tproject.h @@ -9,6 +9,7 @@ class ToonzScene; class TSceneProperties; +class FilePathProperties; #undef DVAPI #undef DVVAR @@ -28,6 +29,8 @@ class DVAPI TProject final : public TSmartObject { bool m_useSubScenePath; TSceneProperties *m_sprop; + FilePathProperties *m_fpProp; + public: // default folders names static const std::string Inputs; @@ -70,6 +73,8 @@ public: void setSceneProperties(const TSceneProperties &sprop); const TSceneProperties &getSceneProperties() const { return *m_sprop; } + FilePathProperties *getFilePathProperties() const { return m_fpProp; } + //????????????????????????????????????????????? void setUseScenePath(std::string folderName, bool on); //????????????????????????????????????????????? diff --git a/toonz/sources/include/toonz/txsheet.h b/toonz/sources/include/toonz/txsheet.h index 2023c457..c4e17940 100644 --- a/toonz/sources/include/toonz/txsheet.h +++ b/toonz/sources/include/toonz/txsheet.h @@ -442,8 +442,9 @@ frame duplication. // cutomized exposseLevel used from LoadLevel command int exposeLevel(int row, int col, TXshLevel *xl, std::vector &fIds_, - int xFrom = -1, int xTo = -1, int step = -1, int inc = -1, - int frameCount = -1, bool doesFileActuallyExist = true); + TFrameId xFrom = TFrameId(), TFrameId xTo = TFrameId(), + int step = -1, int inc = -1, int frameCount = -1, + bool doesFileActuallyExist = true); /*! Exposes level \b \e xl \b \e fids in xsheet starting from cell identified * by \b \e row and \b \e col. diff --git a/toonz/sources/stopmotion/stopmotion.cpp b/toonz/sources/stopmotion/stopmotion.cpp index 2fab24c6..b5223407 100644 --- a/toonz/sources/stopmotion/stopmotion.cpp +++ b/toonz/sources/stopmotion/stopmotion.cpp @@ -160,25 +160,24 @@ QString fidsToString(const std::vector &fids, } else { bool beginBlock = true; for (int f = 0; f < fids.size() - 1; f++) { - int num = fids[f].getNumber(); - char letter = fids[f].getLetter(); - int next_num = fids[f + 1].getNumber(); - char next_letter = fids[f + 1].getLetter(); + int num = fids[f].getNumber(); + QString letter = fids[f].getLetter(); + int next_num = fids[f + 1].getNumber(); + QString next_letter = fids[f + 1].getLetter(); - if (num + 1 == next_num && letter == '\0' && next_letter == '\0') { + if (num + 1 == next_num && letter.isEmpty() && next_letter.isEmpty()) { if (beginBlock) { retStr += QString::number(num) + " - "; beginBlock = false; } } else { retStr += QString::number(num); - if (letter != '\0') retStr += QString(letter); + if (!letter.isEmpty()) retStr += letter; retStr += ", "; beginBlock = true; } } - if (fids.back().getLetter() != '\0') - retStr += QString(fids.back().getLetter()); + if (!fids.back().getLetter().isEmpty()) retStr += fids.back().getLetter(); } return retStr; } diff --git a/toonz/sources/stopmotion/stopmotioncontroller.cpp b/toonz/sources/stopmotion/stopmotioncontroller.cpp index 868a6795..c2abe912 100644 --- a/toonz/sources/stopmotion/stopmotioncontroller.cpp +++ b/toonz/sources/stopmotion/stopmotioncontroller.cpp @@ -670,10 +670,12 @@ FrameNumberLineEdit::FrameNumberLineEdit(QWidget* parent, TFrameId fId, bool acceptLetter) : LineEdit(parent) { setFixedWidth(60); - if (acceptLetter) - m_regexpValidator = - new QRegExpValidator(QRegExp("^\\d{1,4}[A-Za-z]?$"), this); - else + if (acceptLetter) { + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + m_regexpValidator = new QRegExpValidator(QRegExp(regExpStr), this); + TProjectManager* pm = TProjectManager::instance(); + pm->addListener(this); + } else m_regexpValidator = new QRegExpValidator(QRegExp("^\\d{1,4}$"), this); m_regexpValidator_alt = @@ -698,7 +700,7 @@ void FrameNumberLineEdit::updateValidator() { void FrameNumberLineEdit::setValue(TFrameId fId) { QString str; if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { - if (fId.getLetter() != '\0') { + if (!fId.getLetter().isEmpty()) { // need some warning? } str = convertToFrameWithLetter(fId.getNumber(), 3); @@ -724,18 +726,31 @@ TFrameId FrameNumberLineEdit::getValue() { } return TFrameId(f); } else { - QRegExp rx("^(\\d{1,4})([A-Za-z]?)$"); + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + QRegExp rx(regExpStr); int pos = rx.indexIn(text()); if (pos < 0) return TFrameId(); if (rx.cap(2).isEmpty()) return TFrameId(rx.cap(1).toInt()); else - return TFrameId(rx.cap(1).toInt(), rx.cap(2).at(0).toLatin1()); + return TFrameId(rx.cap(1).toInt(), rx.cap(2)); } } //----------------------------------------------------------------------------- +void FrameNumberLineEdit::onProjectSwitched() { + QRegExpValidator* oldValidator = m_regexpValidator; + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + m_regexpValidator = new QRegExpValidator(QRegExp(regExpStr), this); + updateValidator(); + if (oldValidator) delete oldValidator; +} + +void FrameNumberLineEdit::onProjectChanged() { onProjectSwitched(); } + +//----------------------------------------------------------------------------- + void FrameNumberLineEdit::focusInEvent(QFocusEvent *e) { m_textOnFocusIn = text(); } diff --git a/toonz/sources/stopmotion/stopmotioncontroller.h b/toonz/sources/stopmotion/stopmotioncontroller.h index ba65a92f..b21035ff 100644 --- a/toonz/sources/stopmotion/stopmotioncontroller.h +++ b/toonz/sources/stopmotion/stopmotioncontroller.h @@ -9,6 +9,7 @@ #include "toonzqt/lineedit.h" #include "tfilepath.h" +#include "toonz/tproject.h" // TnzQt includes #include "toonzqt/tabbar.h" @@ -71,7 +72,8 @@ class IntLineEdit; // "Show ABC Appendix to the Frame Number in Xsheet Cell" is active. //----------------------------------------------------------------------------- -class FrameNumberLineEdit : public DVGui::LineEdit { +class FrameNumberLineEdit : public DVGui::LineEdit, + public TProjectManager::Listener { Q_OBJECT /* having two validators and switch them according to the preferences*/ QRegExpValidator *m_regexpValidator, *m_regexpValidator_alt; @@ -89,6 +91,10 @@ public: /*! Return an integer with text field value. */ TFrameId getValue(); + // TProjectManager::Listener + void onProjectSwitched() override; + void onProjectChanged() override; + protected: /*! If focus is lost and current text value is out of range emit signal \b editingFinished.*/ diff --git a/toonz/sources/tnztools/tool.cpp b/toonz/sources/tnztools/tool.cpp index ba6b803e..3edb9c01 100644 --- a/toonz/sources/tnztools/tool.cpp +++ b/toonz/sources/tnztools/tool.cpp @@ -102,8 +102,8 @@ TFrameId getNewFrameId(TXshSimpleLevel *sl, int row) { TFrameId fid(row + 1); if (sl->isFid(fid)) { fid = TFrameId(fid.getNumber(), 'a'); - while (fid.getLetter() < 'z' && sl->isFid(fid)) - fid = TFrameId(fid.getNumber(), fid.getLetter() + 1); + while (fid.getLetter().toUtf8().at(0) < 'z' && sl->isFid(fid)) + fid = TFrameId(fid.getNumber(), fid.getLetter().toUtf8().at(0) + 1); } return fid; } @@ -123,9 +123,15 @@ TFrameId getDesiredFId(TXshCellColumn *column, int r0, TXshSimpleLevel *sl, if (neighborFId.isEmptyFrame()) neighborFId = tmpFId; if (maxFId < tmpFId) maxFId = tmpFId; } - if (maxFId.getLetter() && maxFId.getLetter() < 'z' && maxFId == neighborFId) - return TFrameId(maxFId.getNumber(), maxFId.getLetter() + 1); - else + + QByteArray suffix = maxFId.getLetter().toUtf8(); + // increment letter + if (suffix.size() == 1 && + ((suffix.at(0) >= 'A' && suffix.at(0) < 'Z') || + (suffix.at(0) >= 'a' && suffix.at(0) < 'z')) && + maxFId == neighborFId) { + return TFrameId(maxFId.getNumber(), suffix.at(0) + 1); + } else return TFrameId(maxFId.getNumber() + 1); } diff --git a/toonz/sources/tnztools/toolutils.cpp b/toonz/sources/tnztools/toolutils.cpp index a728adb5..8154dc81 100644 --- a/toonz/sources/tnztools/toolutils.cpp +++ b/toonz/sources/tnztools/toolutils.cpp @@ -1827,6 +1827,15 @@ bool ToolUtils::doUpdateXSheet(TXshSimpleLevel *sl, bool ToolUtils::renumberForInsertFId(TXshSimpleLevel *sl, const TFrameId &fid, const TFrameId &maxFid, TXsheet *xsh) { + auto getNextLetter = [](const QString &letter) { + if (letter.isEmpty()) return QString('a'); + if (letter == 'z' || letter == 'Z') return QString(); + QByteArray byteArray = letter.toUtf8(); + // return incrementing the last letter + byteArray.data()[byteArray.size() - 1]++; + return QString::fromUtf8(byteArray); + }; + std::vector fids; std::vector oldFrames; sl->getFids(oldFrames); @@ -1840,10 +1849,10 @@ bool ToolUtils::renumberForInsertFId(TXshSimpleLevel *sl, const TFrameId &fid, for (auto itr = fidsSet.upper_bound(maxFid); itr != fidsSet.end(); ++itr) { if (*itr > tmpFid) break; fIdsToBeShifted.push_back(*itr); - if (fid.getLetter()) { - if ((*itr).getLetter() < 'z') - tmpFid = TFrameId((*itr).getNumber(), - ((*itr).getLetter()) ? (*itr).getLetter() + 1 : 'a'); + if (!fid.getLetter().isEmpty()) { + QString nextLetter = getNextLetter((*itr).getLetter()); + if (!nextLetter.isEmpty()) + tmpFid = TFrameId((*itr).getNumber(), nextLetter); else tmpFid = TFrameId((*itr).getNumber() + 1); } else @@ -1854,11 +1863,10 @@ bool ToolUtils::renumberForInsertFId(TXshSimpleLevel *sl, const TFrameId &fid, for (TFrameId &tmpFid : fids) { if (fIdsToBeShifted.contains(tmpFid)) { - if (fid.getLetter()) { - if (tmpFid.getLetter() < 'z') - tmpFid = - TFrameId(tmpFid.getNumber(), - (tmpFid.getLetter()) ? tmpFid.getLetter() + 1 : 'a'); + if (!fid.getLetter().isEmpty()) { + QString nextLetter = getNextLetter(tmpFid.getLetter()); + if (!nextLetter.isEmpty()) + tmpFid = TFrameId(tmpFid.getNumber(), nextLetter); else tmpFid = TFrameId(tmpFid.getNumber() + 1); } else diff --git a/toonz/sources/toonz/cellselection.cpp b/toonz/sources/toonz/cellselection.cpp index 179773a8..77830580 100644 --- a/toonz/sources/toonz/cellselection.cpp +++ b/toonz/sources/toonz/cellselection.cpp @@ -3013,6 +3013,13 @@ static void dRenumberCells(int col, int r0, int r1) { levelsTable[sl].push_back(std::make_pair(oldFid, newFid)); } } + auto getNextLetter = [](const QString &letter) { + if (letter.isEmpty()) return QString('a'); + QByteArray byteArray = letter.toUtf8(); + // return incrementing the last letter + byteArray.data()[byteArray.size() - 1]++; + return QString::fromUtf8(byteArray); + }; // Ensure renumber consistency in case some destination fid would overwrite // some unrenumbered fid in the level @@ -3022,8 +3029,7 @@ static void dRenumberCells(int col, int r0, int r1) { if (cellsMap.find(it->second) == cellsMap.end() && it->first.getSimpleLevel()->isFid(it->second.getFrameId())) { TFrameId &fid = it->second.m_frameId; - fid = TFrameId(fid.getNumber(), - fid.getLetter() ? fid.getLetter() + 1 : 'a', + fid = TFrameId(fid.getNumber(), getNextLetter(fid.getLetter()), fid.getZeroPadding(), fid.getStartSeqInd()); } } @@ -3218,8 +3224,8 @@ static void createNewDrawing(TXsheet *xsh, int row, int col, TFrameId fid(row + 1); if (sl->isFid(fid)) { fid = TFrameId(fid.getNumber(), 'a'); - while (fid.getLetter() < 'z' && sl->isFid(fid)) - fid = TFrameId(fid.getNumber(), fid.getLetter() + 1); + while (fid.getLetter().toUtf8().at(0) < 'z' && sl->isFid(fid)) + fid = TFrameId(fid.getNumber(), fid.getLetter().toUtf8().at(0) + 1); } // add the new frame sl->setFrame(fid, sl->createEmptyFrame()); diff --git a/toonz/sources/toonz/exportxsheetpdf.cpp b/toonz/sources/toonz/exportxsheetpdf.cpp index 3c3cd1da..2589a272 100644 --- a/toonz/sources/toonz/exportxsheetpdf.cpp +++ b/toonz/sources/toonz/exportxsheetpdf.cpp @@ -874,7 +874,8 @@ void XSheetPDFTemplate::drawCellNumber(QPainter& painter, QRect rect, str = getFrameNumberWithLetters(cell.m_frameId.getNumber()); } else { str = QString::number(cell.m_frameId.getNumber()); - if (cell.m_frameId.getLetter() != '\0') str += cell.m_frameId.getLetter(); + if (!cell.m_frameId.getLetter().isEmpty()) + str += cell.m_frameId.getLetter(); } painter.drawText(rect, Qt::AlignCenter, str); } diff --git a/toonz/sources/toonz/filebrowserpopup.cpp b/toonz/sources/toonz/filebrowserpopup.cpp index 8ee78968..e2701bef 100644 --- a/toonz/sources/toonz/filebrowserpopup.cpp +++ b/toonz/sources/toonz/filebrowserpopup.cpp @@ -15,6 +15,12 @@ #include "convertpopup.h" #include "matchline.h" #include "colormodelbehaviorpopup.h" +//#if defined(x64) +//#include "penciltestpopup.h" // FrameNumberLineEdit +//#else +//#include "penciltestpopup_qt.h" +//#endif +#include "../stopmotion/stopmotioncontroller.h" // FrameNumberLineEdit // TnzQt includes #include "toonzqt/gutil.h" @@ -707,8 +713,8 @@ LoadLevelPopup::LoadLevelPopup() QPushButton *showSubsequenceButton = createShowButton(this); QLabel *subsequenceLabel = new QLabel(tr("Load Subsequence Level"), this); m_subsequenceFrame = new QFrame(this); - m_fromFrame = new DVGui::IntLineEdit(this, 1, 1); - m_toFrame = new DVGui::IntLineEdit(this, 1, 1); + m_fromFrame = new FrameNumberLineEdit(this, TFrameId(1)); + m_toFrame = new FrameNumberLineEdit(this, TFrameId(1)); //----Arrangement in Xsheet m_arrLvlPropWidget = new QWidget(this); @@ -716,8 +722,8 @@ LoadLevelPopup::LoadLevelPopup() QLabel *arrangementLabel = new QLabel(tr("Level Settings & Arrangement in Scene"), this); m_arrangementFrame = new QFrame(this); - m_xFrom = new DVGui::IntLineEdit(this, 1, 1); - m_xTo = new DVGui::IntLineEdit(this, 1, 1); + m_xFrom = new FrameNumberLineEdit(this, TFrameId(1)); + m_xTo = new FrameNumberLineEdit(this, TFrameId(1)); m_stepCombo = new QComboBox(this); m_incCombo = new QComboBox(this); m_posFrom = new DVGui::IntLineEdit(this, 1, 1); @@ -968,12 +974,12 @@ void LoadLevelPopup::onNameSetEditted() { } else { m_notExistLabel->show(); - m_fromFrame->setText("1"); - m_toFrame->setText("1"); + m_fromFrame->setValue(TFrameId(1)); + m_toFrame->setValue(TFrameId(1)); m_subsequenceFrame->setEnabled(true); - m_xFrom->setText("1"); - m_xTo->setText("1"); + m_xFrom->setValue(TFrameId(1)); + m_xTo->setValue(TFrameId(1)); m_levelName->setText(QString::fromStdString(path.getName())); @@ -998,26 +1004,39 @@ void LoadLevelPopup::updatePosTo() { return; } - int xFrom = m_xFrom->text().toInt(); - int xTo = m_xTo->text().toInt(); + TFrameId xFrom = m_xFrom->getValue(); + TFrameId xTo = m_xTo->getValue(); int frameLength; bool isScene = (QString::fromStdString(fp.getType()) == "tnz"); + if (isScene) { // scene does not consider frame suffixes + xFrom = TFrameId(xFrom.getNumber()); + xTo = TFrameId(xTo.getNumber()); + } + + auto frameLengthBetweenFIds = [&]() { + if (xFrom > xTo) return 0; + int ret = xTo.getNumber() - xFrom.getNumber() + 1; + if (!xTo.getLetter().isEmpty()) ret++; + return ret; + }; + //--- if loading the "missing" level if (m_notExistLabel->isVisible()) { int inc = m_incCombo->currentIndex(); if (inc == 0) // Inc = Auto { - frameLength = (xTo - xFrom + 1) * ((m_stepCombo->currentIndex() == 0) - ? 1 - : m_stepCombo->currentIndex()); + frameLength = + frameLengthBetweenFIds() * ((m_stepCombo->currentIndex() == 0) + ? 1 + : m_stepCombo->currentIndex()); } else // Inc =! Auto { int loopAmount; - loopAmount = tceil((double)(xTo - xFrom + 1) / (double)inc); + loopAmount = tceil((double)(frameLengthBetweenFIds()) / (double)inc); frameLength = loopAmount * ((m_stepCombo->currentIndex() == 0) ? inc : m_stepCombo->currentIndex()); @@ -1028,7 +1047,7 @@ void LoadLevelPopup::updatePosTo() { else if (m_incCombo->currentIndex() == 0) // Inc = Auto { if (isScene) { - frameLength = xTo - xFrom + 1; + frameLength = frameLengthBetweenFIds(); } else { std::vector fIds = getCurrentFIds(); //--- If loading the level with sequential files, reuse the list of @@ -1036,32 +1055,23 @@ void LoadLevelPopup::updatePosTo() { if (fIds.size() != 0) { if (m_stepCombo->currentIndex() == 0) // Step = Auto { + frameLength = 0; std::vector::iterator it; - int firstFrame = 0; - int lastFrame = 0; for (it = fIds.begin(); it != fIds.end(); it++) { - if (xFrom <= it->getNumber()) { - firstFrame = it->getNumber(); + if (xFrom <= *it && *it <= xTo) + frameLength++; + else if (xTo < *it) break; - } } - for (it = fIds.begin(); it != fIds.end(); it++) { - if (it->getNumber() <= xTo) { - lastFrame = it->getNumber(); - } - } - frameLength = lastFrame - firstFrame + 1; } else // Step != Auto { std::vector::iterator it; int loopAmount = 0; for (it = fIds.begin(); it != fIds.end(); it++) { - if (xFrom <= it->getNumber() && it->getNumber() <= xTo) - loopAmount++; + if (xFrom <= *it && *it <= xTo) loopAmount++; } frameLength = loopAmount * m_stepCombo->currentIndex(); } - } // loading another type of level such as tlv else { @@ -1074,29 +1084,20 @@ void LoadLevelPopup::updatePosTo() { if (m_stepCombo->currentIndex() == 0) // Step = Auto { + frameLength = 0; TLevel::Iterator it; - int firstFrame = 0; - int lastFrame = 0; for (it = level->begin(); it != level->end(); it++) { - if (xFrom <= it->first.getNumber()) { - firstFrame = it->first.getNumber(); + if (xFrom <= it->first && it->first <= xTo) + frameLength++; + else if (xTo < it->first) break; - } } - for (it = level->begin(); it != level->end(); it++) { - if (it->first.getNumber() <= xTo) { - lastFrame = it->first.getNumber(); - } - } - frameLength = lastFrame - firstFrame + 1; } else // Step != Auto { TLevel::Iterator it; int loopAmount = 0; for (it = level->begin(); it != level->end(); it++) { - if (xFrom <= it->first.getNumber() && - it->first.getNumber() <= xTo) - loopAmount++; + if (xFrom <= it->first && it->first <= xTo) loopAmount++; } frameLength = loopAmount * m_stepCombo->currentIndex(); } @@ -1110,7 +1111,7 @@ void LoadLevelPopup::updatePosTo() { else { int inc = m_incCombo->currentIndex(); int loopAmount; - loopAmount = tceil((double)(xTo - xFrom + 1) / (double)inc); + loopAmount = tceil((double)(frameLengthBetweenFIds()) / (double)inc); frameLength = loopAmount * ((m_stepCombo->currentIndex() == 0) ? inc : m_stepCombo->currentIndex()); @@ -1123,8 +1124,8 @@ void LoadLevelPopup::updatePosTo() { /*! if the from / to values in the subsequent box, update m_xFrom and m_xTo */ void LoadLevelPopup::onSubsequentFrameChanged() { - m_xFrom->setText(m_fromFrame->text()); - m_xTo->setText(m_toFrame->text()); + m_xFrom->setValue(m_fromFrame->getValue()); + m_xTo->setValue(m_toFrame->getValue()); updatePosTo(); } @@ -1192,9 +1193,9 @@ bool LoadLevelPopup::execute() { //---- SubSequent load // if loading the "missing" level if (m_notExistLabel->isVisible()) { - int firstFrameNumber = m_fromFrame->text().toInt(); - int lastFrameNumber = m_toFrame->text().toInt(); - setLoadingLevelRange(firstFrameNumber, lastFrameNumber); + TFrameId firstLoadingFId = m_fromFrame->getValue(); + TFrameId lastLoadingFId = m_toFrame->getValue(); + setLoadingLevelRange(firstLoadingFId, lastLoadingFId); } else if (m_subsequenceFrame->isEnabled() && m_subsequenceFrame->isVisible()) { std::vector fIds = getCurrentFIds(); @@ -1220,11 +1221,10 @@ bool LoadLevelPopup::execute() { return false; } } - int firstFrameNumber = m_fromFrame->text().toInt(); - int lastFrameNumber = m_toFrame->text().toInt(); - if (firstFrame.getNumber() != firstFrameNumber || - lastFrame.getNumber() != lastFrameNumber) - setLoadingLevelRange(firstFrameNumber, lastFrameNumber); + TFrameId firstLoadingFId = m_fromFrame->getValue(); + TFrameId lastLoadingFId = m_toFrame->getValue(); + if (firstFrame != firstLoadingFId || lastFrame != lastLoadingFId) + setLoadingLevelRange(firstLoadingFId, lastLoadingFId); } IoCmd::LoadResourceArguments args(fp); @@ -1237,20 +1237,27 @@ bool LoadLevelPopup::execute() { args.frameIdsSet.push_back(*getCurrentFIdsSet().begin()); else if (m_notExistLabel->isVisible()) { - int firstFrameNumber = m_fromFrame->text().toInt(); - int lastFrameNumber = m_toFrame->text().toInt(); + TFrameId firstLoadingFId = m_fromFrame->getValue(); + TFrameId lastLoadingFId = m_toFrame->getValue(); // putting the Fids in order to avoid LoadInfo later std::vector tmp_fids; - for (int i = firstFrameNumber; i <= lastFrameNumber; i++) { + int i = firstLoadingFId.getNumber(); + if (!firstLoadingFId.getLetter().isEmpty()) { + tmp_fids.push_back(firstLoadingFId); + i++; + } + for (; i <= lastLoadingFId.getNumber(); i++) { tmp_fids.push_back(TFrameId(i)); } + if (!lastLoadingFId.getLetter().isEmpty()) + tmp_fids.push_back(lastLoadingFId); args.frameIdsSet.push_back(tmp_fids); } - int xFrom = m_xFrom->text().toInt(); - if (xFrom) args.xFrom = xFrom; - int xTo = m_xTo->text().toInt(); - if (xTo) args.xTo = xTo; + TFrameId xFrom = m_xFrom->getValue(); + if (!xFrom.isEmptyFrame()) args.xFrom = xFrom; + TFrameId xTo = m_xTo->getValue(); + if (!xTo.isEmptyFrame()) args.xTo = xTo; args.levelName = m_levelName->text().toStdWString(); args.step = m_stepCombo->currentIndex(); @@ -1372,9 +1379,8 @@ void LoadLevelPopup::updateBottomGUI() { disableAll(); return; } else if (ext == "tpl") { - QString str; - m_fromFrame->setText(str.number(1)); - m_toFrame->setText(str.number(1)); + m_fromFrame->setText("1"); + m_toFrame->setText("1"); m_subsequenceFrame->setEnabled(false); m_xFrom->setText("1"); @@ -1420,13 +1426,12 @@ void LoadLevelPopup::updateBottomGUI() { return; } } - - m_fromFrame->setText(QString().number(firstFrame.getNumber())); - m_toFrame->setText(QString().number(lastFrame.getNumber())); + m_fromFrame->setValue(firstFrame); + m_toFrame->setValue(lastFrame); m_subsequenceFrame->setEnabled(true); - m_xFrom->setText(m_fromFrame->text()); - m_xTo->setText(m_toFrame->text()); + m_xFrom->setValue(firstFrame); + m_xTo->setValue(lastFrame); // if some option in the preferences is selected, load the level with // removing diff --git a/toonz/sources/toonz/filebrowserpopup.h b/toonz/sources/toonz/filebrowserpopup.h index 0d39271c..a4e0f1ae 100644 --- a/toonz/sources/toonz/filebrowserpopup.h +++ b/toonz/sources/toonz/filebrowserpopup.h @@ -32,6 +32,7 @@ class QPushButton; class QComboBox; class QGroupBox; class QCheckBox; +class FrameNumberLineEdit; namespace DVGui { class ColorField; @@ -266,11 +267,11 @@ class LoadLevelPopup final : public FileBrowserPopup { Q_OBJECT QFrame *m_subsequenceFrame; - DVGui::IntLineEdit *m_fromFrame, *m_toFrame; + FrameNumberLineEdit *m_fromFrame, *m_toFrame; QWidget *m_arrLvlPropWidget; QFrame *m_arrangementFrame; - DVGui::IntLineEdit *m_xFrom, *m_xTo; + FrameNumberLineEdit *m_xFrom, *m_xTo; QComboBox *m_stepCombo, *m_incCombo; DVGui::IntLineEdit *m_posFrom, *m_posTo; diff --git a/toonz/sources/toonz/filmstrip.cpp b/toonz/sources/toonz/filmstrip.cpp index 97d8c111..59c9d176 100644 --- a/toonz/sources/toonz/filmstrip.cpp +++ b/toonz/sources/toonz/filmstrip.cpp @@ -711,9 +711,9 @@ void FilmstripFrames::paintEvent(QPaintEvent *evt) { } // for sequential frame else { - char letter = fid.getLetter(); - text = QString::number(fid.getNumber()).rightJustified(4, '0') + - (letter != '\0' ? QString(letter) : ""); + QString letter = fid.getLetter(); + text = QString::number(fid.getNumber()).rightJustified(4, '0') + + (!letter.isEmpty() ? letter : ""); } p.drawText(tmp_frameRect.adjusted(0, 0, -3, 2), text, QTextOption(Qt::AlignRight | Qt::AlignBottom)); diff --git a/toonz/sources/toonz/filmstripcommand.cpp b/toonz/sources/toonz/filmstripcommand.cpp index adc8000e..4e983ada 100644 --- a/toonz/sources/toonz/filmstripcommand.cpp +++ b/toonz/sources/toonz/filmstripcommand.cpp @@ -1341,6 +1341,17 @@ public: int getHistoryType() override { return HistoryType::FilmStrip; } }; +QString getNextLetter(const QString &letter) { + // 空なら a を返す + if (letter.isEmpty()) return QString('a'); + // 1文字かつ z または Z ならEmptyを返す + if (letter == 'z' || letter == 'Z') return QString(); + QByteArray byteArray = letter.toUtf8(); + // それ以外の場合、最後の文字をとにかく1進めて返す + byteArray.data()[byteArray.size() - 1]++; + return QString::fromUtf8(byteArray); +}; + } // namespace //============================================================================= @@ -1487,10 +1498,10 @@ void FilmstripCmd::renumber( // make sure that srcFid has not been used. add a letter if this is needed if (tmp.count(tarFid) > 0) { do { - char letter = tarFid.getLetter(); - tarFid = TFrameId(tarFid.getNumber(), letter == 0 ? 'a' : letter + 1); - } while (tarFid.getLetter() <= 'z' && tmp.count(tarFid) > 0); - if (tarFid.getLetter() > 'z') { + tarFid = + TFrameId(tarFid.getNumber(), getNextLetter(tarFid.getLetter())); + } while (!tarFid.getLetter().isEmpty() && tmp.count(tarFid) > 0); + if (tarFid.getLetter().isEmpty()) { // todo: error message return; } @@ -2763,13 +2774,9 @@ void FilmstripCmd::renumberDrawing(TXshSimpleLevel *sl, const TFrameId &oldFid, if (it == fids.end()) return; TFrameId newFid = desiredNewFid; while (std::find(fids.begin(), fids.end(), newFid) != fids.end()) { - char letter = newFid.getLetter(); - if (letter == 'z') return; - if (letter == 0) - letter = 'a'; - else - letter++; - newFid = TFrameId(newFid.getNumber(), letter); + QString nextLetter = getNextLetter(newFid.getLetter()); + if (nextLetter.isEmpty()) return; + newFid = TFrameId(newFid.getNumber(), nextLetter); } *it = newFid; if (Preferences::instance()->isSyncLevelRenumberWithXsheetEnabled()) { diff --git a/toonz/sources/toonz/iocommand.cpp b/toonz/sources/toonz/iocommand.cpp index 3705a5fe..74525043 100644 --- a/toonz/sources/toonz/iocommand.cpp +++ b/toonz/sources/toonz/iocommand.cpp @@ -891,9 +891,9 @@ TXshLevel *loadLevel(ToonzScene *scene, const IoCmd::LoadResourceArguments::ResourceData &rd, const TFilePath &castFolder, int row0, int &col0, int row1, int &col1, bool expose, std::vector &fIds, - int xFrom = -1, int xTo = -1, std::wstring levelName = L"", - int step = -1, int inc = -1, int frameCount = -1, - bool doesFileActuallyExist = true) { + TFrameId xFrom = TFrameId(), TFrameId xTo = TFrameId(), + std::wstring levelName = L"", int step = -1, int inc = -1, + int frameCount = -1, bool doesFileActuallyExist = true) { TFilePath actualPath = scene->decodeFilePath(rd.m_path); LoadLevelUndo *undo = 0; @@ -1022,12 +1022,15 @@ TXshLevel *loadLevel(ToonzScene *scene, // loadResource(scene, path, castFolder, row, col, expose) //--------------------------------------------------------------------------- -TXshLevel *loadResource( - ToonzScene *scene, const IoCmd::LoadResourceArguments::ResourceData &rd, - const TFilePath &castFolder, int row0, int &col0, int row1, int &col1, - bool expose, std::vector fIds = std::vector(), - int xFrom = -1, int xTo = -1, std::wstring levelName = L"", int step = -1, - int inc = -1, int frameCount = -1, bool doesFileActuallyExist = true) { +TXshLevel *loadResource(ToonzScene *scene, + const IoCmd::LoadResourceArguments::ResourceData &rd, + const TFilePath &castFolder, int row0, int &col0, + int row1, int &col1, bool expose, + std::vector fIds = std::vector(), + TFrameId xFrom = TFrameId(), TFrameId xTo = TFrameId(), + std::wstring levelName = L"", int step = -1, + int inc = -1, int frameCount = -1, + bool doesFileActuallyExist = true) { IoCmd::LoadResourceArguments::ResourceData actualRd(rd); actualRd.m_path = scene->decodeFilePath(rd.m_path); diff --git a/toonz/sources/toonz/iocommand.h b/toonz/sources/toonz/iocommand.h index b72a8545..19af69c7 100644 --- a/toonz/sources/toonz/iocommand.h +++ b/toonz/sources/toonz/iocommand.h @@ -127,7 +127,7 @@ public: std::vector loadedLevels; //!< [\p Out] Levels loaded by //! resource loading procedures. - int xFrom, xTo; + TFrameId xFrom, xTo; std::wstring levelName; int step, inc, frameCount; bool doesFileActuallyExist; diff --git a/toonz/sources/toonz/projectpopup.cpp b/toonz/sources/toonz/projectpopup.cpp index 69ee9e86..6847cd98 100644 --- a/toonz/sources/toonz/projectpopup.cpp +++ b/toonz/sources/toonz/projectpopup.cpp @@ -19,10 +19,15 @@ #include "toonzqt/checkbox.h" #include "toonzqt/gutil.h" +// TnzLib +#include "toonz/filepathproperties.h" + // TnzCore includes #include "tsystem.h" #include "tenv.h" #include "tapp.h" +#include "tfilepath.h" + #include "toonz/tscenehandle.h" #include "toonz/toonzscene.h" #include "toonz/preferences.h" @@ -35,9 +40,19 @@ #include #include #include +#include +#include +#include +#include using namespace DVGui; +namespace { + +enum { Rule_Standard = 0, Rule_Custom }; + +} + //=================================================================== TFilePath getDocumentsPath() { @@ -85,6 +100,33 @@ ProjectPopup::ProjectPopup(bool isModal) new CheckBox("*Separate assets into scene sub-folders"); m_useSubSceneCbs->setMaximumHeight(WidgetHeight); + m_rulePreferenceBG = new QButtonGroup(this); + QRadioButton *standardRB = new QRadioButton(tr("Standard"), this); + QRadioButton *customRB = + new QRadioButton(QString("[Experimental] ") + tr("Custom"), this); + m_acceptNonAlphabetSuffixCB = + new CheckBox(tr("Accept Non-alphabet Suffix"), this); + m_letterCountCombo = new QComboBox(this); + + //----- + + m_rulePreferenceBG->addButton(standardRB, Rule_Standard); + m_rulePreferenceBG->addButton(customRB, Rule_Custom); + m_rulePreferenceBG->setExclusive(true); + standardRB->setToolTip(tr( + "In the standard mode files with the following file name are handled as sequencial images:\n\ +[LEVEL_NAME][\".\"or\"_\"][FRAME_NUMBER][SUFFIX].[EXTENSION]\n\ +For [SUFFIX] zero or one occurrences of alphabet (a-z, A-Z) can be used in the standard mode.")); + customRB->setToolTip( + tr("In the custom mode you can customize the file path rules.\n\ +Note that this mode uses regular expression for file name validation and may slow the operation.")); + + m_letterCountCombo->addItem(tr("1"), 1); + m_letterCountCombo->addItem(tr("2"), 2); + m_letterCountCombo->addItem(tr("3"), 3); + m_letterCountCombo->addItem(tr("5"), 5); + m_letterCountCombo->addItem(tr("Unlimited"), 0); + m_settingsLabel = new QLabel(tr("Settings"), this); m_settingsBox = createSettingsBox(); @@ -127,7 +169,12 @@ ProjectPopup::ProjectPopup(bool isModal) SLOT(setVisible(bool))); pm->addListener(this); + + //--------- + connect(m_rulePreferenceBG, SIGNAL(buttonClicked(int)), this, + SLOT(onRulePreferenceToggled(int))); } + //----------------------------------------------------------------------------- QFrame *ProjectPopup::createSettingsBox() { @@ -136,49 +183,72 @@ QFrame *ProjectPopup::createSettingsBox() { TProjectManager *pm = TProjectManager::instance(); - QGridLayout *lay = new QGridLayout(); - lay->setMargin(5); - lay->setHorizontalSpacing(5); - lay->setVerticalSpacing(10); - { - std::vector folderNames; - pm->getFolderNames(folderNames); - int i; - for (i = 0; i < (int)folderNames.size(); i++) { - std::string name = folderNames[i]; - QString qName = QString::fromStdString(name); - FileField *ff = new FileField(0, qName); - m_folderFlds.append(qMakePair(name, ff)); - bool assetFolder = false; - if (qName == "drawings" || qName == "extras" || qName == "inputs") - assetFolder = true; - QLabel *label = new QLabel("+" + qName + (assetFolder ? "*" : ""), this); - lay->addWidget(label, i + 4, 0, Qt::AlignRight | Qt::AlignVCenter); - lay->addWidget(ff, i + 4, 1); - } -/* - std::vector> cbs = { - std::make_tuple(tr("Append $scenepath to +drawings"), - TProject::Drawings), - std::make_tuple(tr("Append $scenepath to +inputs"), TProject::Inputs), - std::make_tuple(tr("Append $scenepath to +extras"), TProject::Extras) }; - int currentRow = lay->rowCount(); + QTabWidget *tabWidget = new QTabWidget(this); - for (int i = 0; i < cbs.size(); ++i) { - auto const &name = std::get<0>(cbs[i]); - auto const &folderName = std::get<1>(cbs[i]); - CheckBox *cb = new CheckBox(name); - cb->setMaximumHeight(WidgetHeight); - lay->addWidget(cb, currentRow + i, 1); - m_useScenePathCbs.append(qMakePair(folderName, cb)); - cb->hide(); + QVBoxLayout *settingsLayout = new QVBoxLayout(); + settingsLayout->setMargin(5); + settingsLayout->setSpacing(10); + { + settingsLayout->addWidget(tabWidget, 1); + + // project folder settings + QWidget *projectFolderPanel = new QWidget(this); + QGridLayout *folderLayout = new QGridLayout(); + folderLayout->setMargin(5); + folderLayout->setHorizontalSpacing(5); + folderLayout->setVerticalSpacing(10); + { + std::vector folderNames; + pm->getFolderNames(folderNames); + int i; + for (i = 0; i < (int)folderNames.size(); i++) { + std::string name = folderNames[i]; + QString qName = QString::fromStdString(name); + FileField *ff = new FileField(0, qName); + m_folderFlds.append(qMakePair(name, ff)); + bool assetFolder = false; + if (qName == "drawings" || qName == "extras" || qName == "inputs") + assetFolder = true; + QLabel *label = new QLabel("+" + qName + (assetFolder ? "*" : ""), this); + folderLayout->addWidget(label, i + 4, 0, Qt::AlignRight | Qt::AlignVCenter); + folderLayout->addWidget(ff, i + 4, 1); + } + int currentRow = folderLayout->rowCount(); + folderLayout->addWidget(m_useSubSceneCbs, currentRow, 1); } -*/ - int currentRow = lay->rowCount(); - lay->addWidget(m_useSubSceneCbs, currentRow, 1); + projectFolderPanel->setLayout(folderLayout); + tabWidget->addTab(projectFolderPanel, tr("Project Folder")); + + + // file path settings + QWidget *filePathPanel = new QWidget(this); + QVBoxLayout *fpLayout = new QVBoxLayout(); + fpLayout->setMargin(5); + fpLayout->setSpacing(10); + { + fpLayout->addWidget(m_rulePreferenceBG->buttons()[0], 0); // standardRB + fpLayout->addWidget(m_rulePreferenceBG->buttons()[1], 0); // customRB + + // add some indent + QGridLayout *customLay = new QGridLayout(); + customLay->setMargin(10); + customLay->setHorizontalSpacing(10); + customLay->setVerticalSpacing(10); + { + customLay->addWidget(m_acceptNonAlphabetSuffixCB, 0, 0, 1, 2); + customLay->addWidget( + new QLabel(tr("Maximum Letter Count For Suffix"), this), 1, 0); + customLay->addWidget(m_letterCountCombo, 1, 1); + } + customLay->setColumnStretch(2, 1); + fpLayout->addLayout(customLay, 0); + fpLayout->addStretch(1); + } + filePathPanel->setLayout(fpLayout); + tabWidget->addTab(filePathPanel, tr("File Path Rules")); } - projectSettingsBox->setLayout(lay); + projectSettingsBox->setLayout(settingsLayout); return projectSettingsBox; } @@ -208,6 +278,18 @@ void ProjectPopup::updateFieldsFromProject(TProject *project) { m_useSubSceneCbs->blockSignals(true); m_useSubSceneCbs->setChecked(project->getUseSubScenePath()); m_useSubSceneCbs->blockSignals(false); + + // file path + FilePathProperties *fpProp = project->getFilePathProperties(); + bool useStandard = fpProp->useStandard(); + bool acceptNonAlphabet = fpProp->acceptNonAlphabetSuffix(); + int letterCount = fpProp->letterCountForSuffix(); + m_rulePreferenceBG->button((useStandard) ? Rule_Standard : Rule_Custom) + ->setChecked(true); + onRulePreferenceToggled((useStandard) ? Rule_Standard : Rule_Custom); + m_acceptNonAlphabetSuffixCB->setChecked(acceptNonAlphabet); + m_letterCountCombo->setCurrentIndex( + m_letterCountCombo->findData(letterCount)); } //----------------------------------------------------------------------------- @@ -229,6 +311,20 @@ void ProjectPopup::updateProjectFromFields(TProject *project) { */ bool useScenePath = m_useSubSceneCbs->isChecked(); project->setUseSubScenePath(useScenePath); + + // file path + FilePathProperties *fpProp = project->getFilePathProperties(); + bool useStandard = m_rulePreferenceBG->checkedId() == Rule_Standard; + bool acceptNonAlphabet = m_acceptNonAlphabetSuffixCB->isChecked(); + int letterCount = m_letterCountCombo->currentData().toInt(); + fpProp->setUseStandard(useStandard); + fpProp->setAcceptNonAlphabetSuffix(acceptNonAlphabet); + fpProp->setLetterCountForSuffix(letterCount); + + if (TFilePath::setFilePathProperties(useStandard, acceptNonAlphabet, + letterCount)) + DvDirModel::instance()->refreshFolderChild(QModelIndex()); // refresh all + TProjectManager::instance()->notifyProjectChanged(); } @@ -246,6 +342,13 @@ void ProjectPopup::showEvent(QShowEvent *) { updateFieldsFromProject(currentProject.getPointer()); } +//----------------------------------------------------------------------------- + +void ProjectPopup::onRulePreferenceToggled(int id) { + m_acceptNonAlphabetSuffixCB->setEnabled((id == Rule_Custom)); + m_letterCountCombo->setEnabled((id == Rule_Custom)); +} + //============================================================================= /*! \class ProjectSettingsPopup \brief The ProjectSettingsPopup class provides a dialog to @@ -269,7 +372,7 @@ ProjectSettingsPopup::ProjectSettingsPopup() : ProjectPopup(false) { int i; for (i = 0; i < m_folderFlds.size(); i++) { FileField *ff = m_folderFlds[i].second; - connect(ff, SIGNAL(pathChanged()), this, SLOT(onFolderChanged())); + connect(ff, SIGNAL(pathChanged()), this, SLOT(onSomethingChanged())); } /* for (i = 0; i < m_useScenePathCbs.size(); i++) { @@ -278,8 +381,16 @@ ProjectSettingsPopup::ProjectSettingsPopup() : ProjectPopup(false) { SLOT(onUseSceneChekboxChanged(int))); } */ - connect(m_useSubSceneCbs, SIGNAL(stateChanged(int)), this, - SLOT(onUseSceneChekboxChanged(int))); + + connect(m_useSubSceneCbs, SIGNAL(stateChanged(int)), this, SLOT(onSomethingChanged())); + + // file path settings + connect(m_rulePreferenceBG, SIGNAL(buttonClicked(int)), this, + SLOT(onSomethingChanged())); + connect(m_acceptNonAlphabetSuffixCB, SIGNAL(clicked(bool)), this, + SLOT(onSomethingChanged())); + connect(m_letterCountCombo, SIGNAL(activated(int)), this, + SLOT(onSomethingChanged())); } //----------------------------------------------------------------------------- @@ -374,21 +485,7 @@ void ProjectSettingsPopup::onProjectChanged() { //----------------------------------------------------------------------------- -void ProjectSettingsPopup::onFolderChanged() { - TProjectP project = TProjectManager::instance()->getCurrentProject(); - updateProjectFromFields(project.getPointer()); - try { - project->save(); - } catch (TSystemException se) { - DVGui::warning(QString::fromStdWString(se.getMessage())); - return; - } - DvDirModel::instance()->refreshFolder(project->getProjectFolder()); -} - -//----------------------------------------------------------------------------- - -void ProjectSettingsPopup::onUseSceneChekboxChanged(int) { +void ProjectSettingsPopup::onSomethingChanged() { TProjectP project = TProjectManager::instance()->getCurrentProject(); updateProjectFromFields(project.getPointer()); try { @@ -556,6 +653,12 @@ void ProjectCreatePopup::showEvent(QShowEvent *) { setSizePolicy(sizePolicy); setFixedSize(width(), height()); setSizeGripEnabled(false); + + // default file path settings + m_rulePreferenceBG->button(Rule_Standard)->setChecked(true); + onRulePreferenceToggled(Rule_Standard); + m_acceptNonAlphabetSuffixCB->setChecked(false); + m_letterCountCombo->setCurrentIndex(m_letterCountCombo->findData(1)); } void ProjectCreatePopup::setPath(QString path) { diff --git a/toonz/sources/toonz/projectpopup.h b/toonz/sources/toonz/projectpopup.h index f1e48d4e..ffcf8624 100644 --- a/toonz/sources/toonz/projectpopup.h +++ b/toonz/sources/toonz/projectpopup.h @@ -16,11 +16,12 @@ namespace DVGui { class FileField; class LineEdit; class CheckBox; -} +} // namespace DVGui class QComboBox; class QGridLayout; class QGroupBox; +class QButtonGroup; //============================================================================= // ProjectPopup @@ -46,6 +47,11 @@ protected: QFrame *m_settingsBox; QPushButton *m_showSettingsButton; + // file path settings + QButtonGroup *m_rulePreferenceBG; + DVGui::CheckBox *m_acceptNonAlphabetSuffixCB; + QComboBox *m_letterCountCombo; + public: ProjectPopup(bool isModal); // da TProjectManager::Listener @@ -60,6 +66,9 @@ public: protected: void showEvent(QShowEvent *) override; + +protected slots: + void onRulePreferenceToggled(int); }; //============================================================================= @@ -75,8 +84,7 @@ public: ProjectSettingsPopup(); public slots: - void onFolderChanged(); - void onUseSceneChekboxChanged(int); + void onSomethingChanged(); void projectChanged(); void onProjectChanged() override; diff --git a/toonz/sources/toonz/tvpjson_io.cpp b/toonz/sources/toonz/tvpjson_io.cpp index cb19064d..1078bfa7 100644 --- a/toonz/sources/toonz/tvpjson_io.cpp +++ b/toonz/sources/toonz/tvpjson_io.cpp @@ -250,12 +250,13 @@ void TvpJsonLayer::build(int index, ToonzScene* scene, TXshCellColumn* column) { if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) instance_name = getFrameNumberWithLetters(fid.getNumber()); else { - std::string frameNumber(""); + QString frameNumber(""); // set number - if (fid.getNumber() >= 0) frameNumber = std::to_string(fid.getNumber()); + if (fid.getNumber() >= 0) + frameNumber = QString::number(fid.getNumber()); // add letter - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); - instance_name = QString::fromStdString(frameNumber); + if (!fid.getLetter().isEmpty()) frameNumber += fid.getLetter(); + instance_name = frameNumber; } fid.setZeroPadding(frameFormats[cell.m_level.getPointer()].first); diff --git a/toonz/sources/toonz/xdtsio.cpp b/toonz/sources/toonz/xdtsio.cpp index a3dbecfe..a613f7e6 100644 --- a/toonz/sources/toonz/xdtsio.cpp +++ b/toonz/sources/toonz/xdtsio.cpp @@ -60,21 +60,23 @@ void XdtsHeader::write(QJsonObject &json) const { //----------------------------------------------------------------------------- TFrameId XdtsFrameDataItem::str2Fid(const QString &str) const { + if (str.isEmpty()) return TFrameId::EMPTY_FRAME; bool ok; int frame = str.toInt(&ok); if (ok) return TFrameId(frame); - // separate the last word as suffix - frame = str.left(str.size() - 1).toInt(&ok); - if (!ok) return TFrameId(-1); // EMPTY - if (!str[str.size() - 1].isLetter()) return TFrameId(-1); // EMPTY - char c = str[str.size() - 1].toLatin1(); - - return TFrameId(frame, c); + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + QRegExp rx(regExpStr); + int pos = rx.indexIn(str); + if (pos < 0) return TFrameId(); + if (rx.cap(2).isEmpty()) + return TFrameId(rx.cap(1).toInt()); + else + return TFrameId(rx.cap(1).toInt(), rx.cap(2)); } QString XdtsFrameDataItem::fid2Str(const TFrameId &fid) const { - if (fid.getLetter() == 0) return QString::number(fid.getNumber()); - return QString::number(fid.getNumber()) + QString(fid.getLetter()); + if (fid.getLetter().isEmpty()) return QString::number(fid.getNumber()); + return QString::number(fid.getNumber()) + fid.getLetter(); } void XdtsFrameDataItem::read(const QJsonObject &json) { diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index a4cb28fe..d8ae6659 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -301,7 +301,8 @@ void parse(const QString &text, std::wstring &levelName, TFrameId &fid) { QRegExp spaces("\\t|\\s"); QRegExp numbers("\\d+"); QRegExp characters("[^\\d+]"); - QRegExp fidWithSuffix("([0-9]+)([a-z]?)"); + QRegExp fidWithSuffix(TFilePath::fidRegExpStr()); + // QRegExp fidWithSuffix("([0-9]+)([a-z]?)"); QString str = text; // remove final spaces @@ -320,9 +321,8 @@ void parse(const QString &text, std::wstring &levelName, TFrameId &fid) { fid = TFrameId(str.toInt()); } else if (fidWithSuffix.exactMatch(str)) { levelName = L""; - fid = TFrameId( - fidWithSuffix.cap(1).toInt(), - fidWithSuffix.cap(2) == "" ? 0 : fidWithSuffix.cap(2).toLatin1()[0]); + fid = TFrameId(fidWithSuffix.cap(1).toInt(), fidWithSuffix.cap(2)); + // fidWithSuffix.cap(2) == "" ? 0 : fidWithSuffix.cap(2).toLatin1()[0]); } else if (str.contains(characters)) { levelName = text.toStdWString(); fid = TFrameId::NO_FRAME; @@ -336,9 +336,8 @@ void parse(const QString &text, std::wstring &levelName, TFrameId &fid) { } else if (fidWithSuffix.exactMatch(lastString)) { QString firstString = str.left(lastSpaceIndex); levelName = firstString.toStdWString(); - fid = TFrameId( - fidWithSuffix.cap(1).toInt(), - fidWithSuffix.cap(2) == "" ? 0 : fidWithSuffix.cap(2).toLatin1()[0]); + fid = TFrameId(fidWithSuffix.cap(1).toInt(), fidWithSuffix.cap(2)); + // fidWithSuffix.cap(2) == "" ? 0 : fidWithSuffix.cap(2).toLatin1()[0]); } else if (lastString.contains(characters)) { levelName = text.toStdWString(); fid = TFrameId::NO_FRAME; @@ -654,9 +653,9 @@ void RenameCellField::showInRowCol(int row, int col, bool multiColumnSelected) { : QString::fromStdWString(levelName) + QString(" ") + m_viewer->getFrameNumberWithLetters(fid.getNumber())); else { - std::string frameNumber(""); - if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber()); - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); + QString frameNumber(""); + if (fid.getNumber() > 0) frameNumber = QString::number(fid.getNumber()); + if (!fid.getLetter().isEmpty()) frameNumber += fid.getLetter(); // get text from sound text level if (cell.m_level->getType() == TXshLevelType::SND_TXT_XSHLEVEL) { @@ -669,12 +668,12 @@ void RenameCellField::showInRowCol(int row, int col, bool multiColumnSelected) { } // other level types else { - setText((frameNumber.empty()) + setText((frameNumber.isEmpty()) ? QString::fromStdWString(levelName) : (multiColumnSelected) - ? QString::fromStdString(frameNumber) + ? frameNumber : QString::fromStdWString(levelName) + QString(" ") + - QString::fromStdString(frameNumber)); + frameNumber); } } selectAll(); @@ -762,8 +761,8 @@ void RenameCellField::renameSoundTextColumn(TXshSoundTextColumn *sndTextCol, //----------------------------------------------------------------------------- void RenameCellField::renameCell() { - QString s = text(); - std::wstring newName = s.toStdWString(); + QString newName = text(); + // std::wstring newName = s.toStdWString(); setText(""); @@ -777,16 +776,16 @@ void RenameCellField::renameCell() { xsheet->getColumn(m_col)->getSoundTextColumn()) { TXshSoundTextColumn *sndTextCol = xsheet->getColumn(m_col)->getSoundTextColumn(); - renameSoundTextColumn(sndTextCol, s); + renameSoundTextColumn(sndTextCol, newName); return; } // convert the last one digit of the frame number to alphabet // Ex. 12 -> 1B 21 -> 2A 30 -> 3 if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) - parse_with_letter(QString::fromStdWString(newName), levelName, fid); + parse_with_letter(newName, levelName, fid); else { - parse(QString::fromStdWString(newName), levelName, fid); + parse(newName, levelName, fid); } bool animationSheetEnabled = Preferences::instance()->isAnimationSheetEnabled(); @@ -2012,12 +2011,12 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) fnum = m_viewer->getFrameNumberWithLetters(fid.getNumber()); else { - std::string frameNumber(""); + QString frameNumber(""); // set number - if (fid.getNumber() >= 0) frameNumber = std::to_string(fid.getNumber()); + if (fid.getNumber() >= 0) frameNumber = QString::number(fid.getNumber()); // add letter - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); - fnum = QString::fromStdString(frameNumber); + if (!fid.getLetter().isEmpty()) frameNumber += fid.getLetter(); + fnum = frameNumber; } int alignFlag = @@ -2351,9 +2350,9 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, TFrameId fid = cell.m_frameId; std::wstring levelName = cell.m_level->getName(); - std::string frameNumber(""); - if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber()); - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); + // QString frameNumber(""); + // if (fid.getNumber() > 0) frameNumber = QString::number(fid.getNumber()); + // if (fid.getLetter() != 0) frameNumber += fid.getLetter(); QRect nameRect = o->rect(PredefinedRect::CELL_NAME).translated(QPoint(x, y)); @@ -2395,13 +2394,12 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, numberStr = m_viewer->getFrameNumberWithLetters(fid.getNumber()); p.drawText(nameRect, Qt::AlignRight | Qt::AlignBottom, numberStr); } else { - std::string frameNumber(""); + QString frameNumber(""); // set number - if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber()); + if (fid.getNumber() > 0) frameNumber = QString::number(fid.getNumber()); // add letter - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); - numberStr = QString::fromStdString(frameNumber); - p.drawText(nameRect, Qt::AlignRight | Qt::AlignBottom, numberStr); + if (!fid.getLetter().isEmpty()) frameNumber += fid.getLetter(); + p.drawText(nameRect, Qt::AlignRight | Qt::AlignBottom, frameNumber); } } @@ -3085,14 +3083,13 @@ void CellArea::mouseMoveEvent(QMouseEvent *event) { : QString::fromStdWString(levelName) + QString(" ") + m_viewer->getFrameNumberWithLetters(fid.getNumber()); } else { - std::string frameNumber(""); - if (fid.getNumber() >= 0) frameNumber = std::to_string(fid.getNumber()); - if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter()); + QString frameNumber(""); + if (fid.getNumber() >= 0) frameNumber = QString::number(fid.getNumber()); + if (!fid.getLetter().isEmpty()) frameNumber += fid.getLetter(); m_tooltip = - QString((frameNumber.empty()) - ? QString::fromStdWString(levelName) - : QString::fromStdWString(levelName) + QString(" ") + - QString::fromStdString(frameNumber)); + QString((frameNumber.isEmpty()) ? QString::fromStdWString(levelName) + : QString::fromStdWString(levelName) + + QString(" ") + frameNumber); } } else if (isSoundColumn && o->rect(PredefinedRect::PREVIEW_TRACK) diff --git a/toonz/sources/toonz/xsheetdragtool.cpp b/toonz/sources/toonz/xsheetdragtool.cpp index 68edf662..3de23076 100644 --- a/toonz/sources/toonz/xsheetdragtool.cpp +++ b/toonz/sources/toonz/xsheetdragtool.cpp @@ -551,7 +551,7 @@ public: int i; for (i = 1; i < count; i++) if (m_sourceCells[i].m_level != cell.m_level || - m_sourceCells[i].m_frameId.getLetter() != 0) + !m_sourceCells[i].m_frameId.getLetter().isEmpty()) return; // check if all the selected cells have the same frame number diff --git a/toonz/sources/toonzlib/CMakeLists.txt b/toonz/sources/toonzlib/CMakeLists.txt index f792f1e2..aea95e93 100644 --- a/toonz/sources/toonzlib/CMakeLists.txt +++ b/toonz/sources/toonzlib/CMakeLists.txt @@ -162,6 +162,7 @@ set(HEADERS ../include/toonz/preferencesitemids.h ../include/toonz/txsheetcolumnchange.h ../include/toonz/expressionreferencemonitor.h + ../include/toonz/filepathproperties.h ) set(SOURCES @@ -320,6 +321,7 @@ set(SOURCES txshmeshcolumn.cpp textureutils.cpp boardsettings.cpp + filepathproperties.cpp ) if(BUILD_TARGET_WIN) diff --git a/toonz/sources/toonzlib/scriptbinding_scene.cpp b/toonz/sources/toonzlib/scriptbinding_scene.cpp index 1f18b268..9b1312a0 100644 --- a/toonz/sources/toonzlib/scriptbinding_scene.cpp +++ b/toonz/sources/toonzlib/scriptbinding_scene.cpp @@ -206,10 +206,10 @@ QScriptValue Scene::getCell(int row, int col) { if (sl) { QScriptValue level = create(engine(), new Level(sl)); QScriptValue fid; - if (cell.m_frameId.getLetter() == 0) + if (cell.m_frameId.getLetter().isEmpty()) fid = cell.m_frameId.getNumber(); else - fid = QString::fromStdString(cell.m_frameId.expand()); + fid = QString::fromStdString(cell.m_frameId.expand()); QScriptValue result = engine()->newObject(); result.setProperty("level", level); result.setProperty("fid", fid); diff --git a/toonz/sources/toonzlib/tproject.cpp b/toonz/sources/toonzlib/tproject.cpp index 785abce6..0cbefbab 100644 --- a/toonz/sources/toonzlib/tproject.cpp +++ b/toonz/sources/toonzlib/tproject.cpp @@ -10,6 +10,7 @@ #include "toonz/toonzfolders.h" #include "toonz/cleanupparameters.h" #include "toonz/preferences.h" +#include "toonz/filepathproperties.h" // TnzBase includes #include "tenv.h" @@ -17,6 +18,7 @@ // TnzCore includes #include "tsystem.h" #include "tstream.h" +#include "tfilepath.h" #include "tfilepath_io.h" #include "tconvert.h" @@ -321,11 +323,18 @@ TFilePath getDesktopPath() { \see TProjectManager and TOStream. */ -TProject::TProject() : m_name(), m_path(), m_sprop(new TSceneProperties()) {} +TProject::TProject() + : m_name() + , m_path() + , m_sprop(new TSceneProperties()) + , m_fpProp(new FilePathProperties()) {} //------------------------------------------------------------------- -TProject::~TProject() { delete m_sprop; } +TProject::~TProject() { + delete m_sprop; + delete m_fpProp; +} //------------------------------------------------------------------- /*! Associates the \b name to the specified \b path. @@ -615,6 +624,13 @@ bool TProject::save(const TFilePath &projectPath) { os.openChild("sceneProperties"); getSceneProperties().saveData(os); os.closeChild(); + + if (!getFilePathProperties()->isDefault()) { + os.openChild("filePathProperties"); + getFilePathProperties()->saveData(os); + os.closeChild(); + } + os.closeChild(); // crea (se necessario) le directory relative ai vari folder @@ -725,6 +741,9 @@ void TProject::load(const TFilePath &projectPath) { } setSceneProperties(sprop); is.matchEndTag(); + } else if (tagName == "filePathProperties") { + m_fpProp->loadData(is); + is.matchEndTag(); } } @@ -1038,6 +1057,12 @@ TProjectP TProjectManager::getCurrentProject() { assert(TProject::isAProjectPath(fp)); currentProject = new TProject(); currentProject->load(fp); + + // update TFilePath condition on loading the current project + FilePathProperties *fpProp = currentProject->getFilePathProperties(); + TFilePath::setFilePathProperties(fpProp->useStandard(), + fpProp->acceptNonAlphabetSuffix(), + fpProp->letterCountForSuffix()); } return currentProject; } diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index 6b070133..db9425c8 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -1061,8 +1061,8 @@ int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, bool overwrite) { //----------------------------------------------------------------------------- // customized version for load level popup int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, - std::vector &fIds_, int xFrom, int xTo, - int step, int inc, int frameCount, + std::vector &fIds_, TFrameId xFrom, + TFrameId xTo, int step, int inc, int frameCount, bool doesFileActuallyExist) { if (!xl) return 0; std::vector fids; @@ -1104,7 +1104,7 @@ int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, { std::vector::iterator it; it = fids.begin(); - while (it->getNumber() < xFrom) it++; + while (*it < xFrom) it++; if (step == 0) // Step = Auto { @@ -1112,14 +1112,12 @@ int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, next_it = it; next_it++; - int startFrame = it->getNumber(); - - for (int f = startFrame; f < startFrame + frameCount; f++) { - if (next_it != fids.end() && f >= next_it->getNumber()) { + for (int f = 0; f < frameCount; f++) { + setCell(row++, col, TXshCell(xl, *it)); + if (next_it != fids.end()) { it++; next_it++; } - setCell(row++, col, TXshCell(xl, *it)); } } @@ -1143,7 +1141,7 @@ int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, loopCount = frameCount / step; for (int loop = 0; loop < loopCount; loop++) { - TFrameId id(xFrom + loop * inc, fids.begin()->getLetter()); + TFrameId id(xFrom.getNumber() + loop * inc, xFrom.getLetter()); for (int s = 0; s < step; s++) { setCell(row++, col, TXshCell(xl, id)); } diff --git a/toonz/sources/toonzlib/txshlevelcolumn.cpp b/toonz/sources/toonzlib/txshlevelcolumn.cpp index 1bc1aae8..affee586 100644 --- a/toonz/sources/toonzlib/txshlevelcolumn.cpp +++ b/toonz/sources/toonzlib/txshlevelcolumn.cpp @@ -17,24 +17,17 @@ TFrameId qstringToFrameId(QString str) { return TFrameId::EMPTY_FRAME; else if (str == "-" || str == "-2") return TFrameId::NO_FRAME; - TFrameId fid; - int s = 0; - QString number; - char letter(0); - for (s = 0; s < str.size(); s++) { - QChar c = str.at(s); - if (c.isNumber()) number.append(c); -#if QT_VERSION >= 0x050500 - else - letter = c.toLatin1(); -#else - else - letter = c.toAscii(); -#endif - } - return TFrameId(number.toInt(), letter); -} + + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + QRegExp rx(regExpStr); + int pos = rx.indexIn(str); + if (pos < 0) return TFrameId(); + if (rx.cap(2).isEmpty()) + return TFrameId(rx.cap(1).toInt()); + else + return TFrameId(rx.cap(1).toInt(), rx.cap(2)); } +} // namespace //----------------------------------------------------------------------------- @@ -123,13 +116,13 @@ void TXshLevelColumn::loadData(TIStream &is) { while (is.openChild(tagName)) { if (tagName == "cell") { TPersist *p = 0; - QString str; + std::string str; int row = 1, rowCount = 1, increment = 0; TFilePath path; is >> row >> rowCount >> p >> str >> increment; - TFrameId fid = qstringToFrameId(str); - assert((fid.getLetter() == 0 && rowCount >= 0) || - (fid.getLetter() != 0 && rowCount == 1)); + TFrameId fid = qstringToFrameId(QString::fromStdString(str)); + assert((fid.getLetter().isEmpty() && rowCount >= 0) || + (!fid.getLetter().isEmpty() && rowCount == 1)); TXshLevel *xshLevel = dynamic_cast(p); if (xshLevel) { int fidNumber = fid.getNumber(); @@ -182,11 +175,11 @@ void TXshLevelColumn::saveData(TOStream &os) { int n = 1, inc = 0, dr = fid.getNumber(); // If fid has not letter save more than one cell and its incrementation; // otherwise save one cell. - if (r < r1 && fid.getLetter() == 0) { + if (r < r1 && fid.getLetter().isEmpty()) { TXshCell cell2 = getCell(r + 1); TFrameId fid2 = cell2.m_frameId; if (cell2.m_level.getPointer() == cell.m_level.getPointer() && - fid2.getLetter() == 0) { + fid2.getLetter().isEmpty()) { inc = cell2.m_frameId.getNumber() - dr; n++; for (;;) { @@ -194,7 +187,7 @@ void TXshLevelColumn::saveData(TOStream &os) { cell2 = getCell(r + n); TFrameId fid2 = cell2.m_frameId; if (cell2.m_level.getPointer() != cell.m_level.getPointer() || - fid2.getLetter() != 0) + !fid2.getLetter().isEmpty()) break; if (fid2.getNumber() != dr + n * inc) break; n++; diff --git a/toonz/sources/toonzlib/txshmeshcolumn.cpp b/toonz/sources/toonzlib/txshmeshcolumn.cpp index 3af1bdca..76302db2 100644 --- a/toonz/sources/toonzlib/txshmeshcolumn.cpp +++ b/toonz/sources/toonzlib/txshmeshcolumn.cpp @@ -24,28 +24,16 @@ TFrameId qstringToFrameId(QString str) { else if (str == "-" || str == "-2") return TFrameId::NO_FRAME; - TFrameId fid; - - QString number; - char letter(0); - - int s, strSize = str.size(); - for (s = 0; s < strSize; ++s) { - QChar c = str.at(s); - - if (c.isNumber()) - number.append(c); - else -#if QT_VERSION >= 0x050500 - letter = c.toLatin1(); -#else - letter = c.toAscii(); -#endif - } - - return TFrameId(number.toInt(), letter); -} + QString regExpStr = QString("^%1$").arg(TFilePath::fidRegExpStr()); + QRegExp rx(regExpStr); + int pos = rx.indexIn(str); + if (pos < 0) return TFrameId(); + if (rx.cap(2).isEmpty()) + return TFrameId(rx.cap(1).toInt()); + else + return TFrameId(rx.cap(1).toInt(), rx.cap(2)); } +} // namespace //******************************************************************************* // TXshMeshColumn implementation @@ -94,12 +82,12 @@ void TXshMeshColumn::saveData(TOStream &os) { // If fid has no letter save more than one cell and its increment - // otherwise save just one cell - if (r < r1 && fid.getLetter() == 0) { + if (r < r1 && fid.getLetter().isEmpty()) { TXshCell cell2 = getCell(r + 1); TFrameId fid2 = cell2.m_frameId; if (cell2.m_level.getPointer() == cell.m_level.getPointer() && - fid2.getLetter() == 0) { + fid2.getLetter().isEmpty()) { inc = cell2.m_frameId.getNumber() - dr; for (++n;; ++n) { if (r + n > r1) break; @@ -108,7 +96,7 @@ void TXshMeshColumn::saveData(TOStream &os) { TFrameId fid2 = cell2.m_frameId; if (cell2.m_level.getPointer() != cell.m_level.getPointer() || - fid2.getLetter() != 0) + !fid2.getLetter().isEmpty()) break; if (fid2.getNumber() != dr + n * inc) break; @@ -150,16 +138,16 @@ void TXshMeshColumn::loadData(TIStream &is) { while (is.openChild(tagName)) { if (tagName == "cell") { TPersist *p = 0; - QString str; + std::string str; int row = 1, rowCount = 1, increment = 0; TFilePath path; is >> row >> rowCount >> p >> str >> increment; - TFrameId fid = qstringToFrameId(str); - assert((fid.getLetter() == 0 && rowCount >= 0) || - (fid.getLetter() != 0 && rowCount == 1)); + TFrameId fid = qstringToFrameId(QString::fromStdString(str)); + assert((fid.getLetter().isEmpty() && rowCount >= 0) || + (!fid.getLetter().isEmpty() && rowCount == 1)); TXshLevel *xshLevel = dynamic_cast(p); if (xshLevel) { diff --git a/toonz/sources/toonzlib/txshsimplelevel.cpp b/toonz/sources/toonzlib/txshsimplelevel.cpp index 9fed4c84..2b77d696 100644 --- a/toonz/sources/toonzlib/txshsimplelevel.cpp +++ b/toonz/sources/toonzlib/txshsimplelevel.cpp @@ -485,7 +485,8 @@ int TXshSimpleLevel::guessStep() const { TFrameId firstFid = *ft++, secondFid = *ft++; - if (firstFid.getLetter() != 0 || secondFid.getLetter() != 0) return 1; + if (!firstFid.getLetter().isEmpty() || !secondFid.getLetter().isEmpty()) + return 1; int step = secondFid.getNumber() - firstFid.getNumber(); if (step == 1) return 1; @@ -494,7 +495,7 @@ int TXshSimpleLevel::guessStep() const { // (cerco di limitare il numero di volte in cui devo controllare tutta la // lista) TFrameId lastFid = *m_frames.rbegin(); - if (lastFid.getLetter() != 0) return 1; + if (!lastFid.getLetter().isEmpty()) return 1; if (lastFid.getNumber() != firstFid.getNumber() + step * (frameCount - 1)) return 1; @@ -502,7 +503,7 @@ int TXshSimpleLevel::guessStep() const { for (int i = 2; ft != m_frames.end(); ++ft, ++i) { const TFrameId &fid = *ft; - if (fid.getLetter() != 0) return 1; + if (!fid.getLetter().isEmpty()) return 1; if (fid.getNumber() != firstFid.getNumber() + step * i) return 1; } From 6f8de8cbf34b1bcd6ffe698f77139aab51855f0e Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Fri, 3 Dec 2021 14:59:53 +0900 Subject: [PATCH 06/18] fix zeraryfx bugs --- toonz/sources/toonz/iocommand.cpp | 3 ++- toonz/sources/toonz/levelcommand.cpp | 8 ++++++++ toonz/sources/toonz/xshcolumnviewer.cpp | 2 +- toonz/sources/toonzlib/txshzeraryfxcolumn.cpp | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/toonz/sources/toonz/iocommand.cpp b/toonz/sources/toonz/iocommand.cpp index 74525043..a3318854 100644 --- a/toonz/sources/toonz/iocommand.cpp +++ b/toonz/sources/toonz/iocommand.cpp @@ -324,7 +324,8 @@ bool beforeCellsInsert(TXsheet *xsh, int row, int &col, int rowCount, for (i = 0; i < rowCount && xsh->getCell(row + i, col).isEmpty(); i++) { } - int type = column ? column->getColumnType() : newLevelColumnType; + int type = (column && !column->isEmpty()) ? column->getColumnType() + : newLevelColumnType; // If some used cells in range or column type mismatch must insert a column. if (col < 0 || i < rowCount || newLevelColumnType != type) { col += 1; diff --git a/toonz/sources/toonz/levelcommand.cpp b/toonz/sources/toonz/levelcommand.cpp index bc7c0666..164524c8 100644 --- a/toonz/sources/toonz/levelcommand.cpp +++ b/toonz/sources/toonz/levelcommand.cpp @@ -512,6 +512,14 @@ void LevelCmd::addMissingLevelsToCast(const QList &columns) { } void LevelCmd::addMissingLevelsToCast(std::set &levels) { + // remove zerary fx levels which are not registered in the cast + for (auto it = levels.begin(); it != levels.end();) { + if ((*it)->getZeraryFxLevel()) + it = levels.erase(it); + else + ++it; + } + if (levels.empty()) return; TUndoManager::manager()->beginBlock(); TLevelSet *levelSet = diff --git a/toonz/sources/toonz/xshcolumnviewer.cpp b/toonz/sources/toonz/xshcolumnviewer.cpp index d38a93af..38b57655 100644 --- a/toonz/sources/toonz/xshcolumnviewer.cpp +++ b/toonz/sources/toonz/xshcolumnviewer.cpp @@ -1043,7 +1043,7 @@ void ColumnArea::DrawHeader::drawColumnName() const { // ZeraryFx columns store name elsewhere TXshZeraryFxColumn *zColumn = dynamic_cast(column); - if (zColumn) + if (zColumn && !isEmpty) name = ::to_string(zColumn->getZeraryColumnFx()->getZeraryFx()->getName()); QRect columnName = o->rect((col < 0) ? PredefinedRect::CAMERA_LAYER_NAME diff --git a/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp b/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp index 03c125f0..b7940d50 100644 --- a/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp +++ b/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp @@ -29,7 +29,8 @@ TXshZeraryFxColumn::TXshZeraryFxColumn(int frameCount) TXshZeraryFxColumn::TXshZeraryFxColumn(const TXshZeraryFxColumn &src) : m_zeraryColumnFx(new TZeraryColumnFx()) - , m_zeraryFxLevel(new TXshZeraryFxLevel()) { + , m_zeraryFxLevel(new TXshZeraryFxLevel()) + , m_iconVisible(false) { m_zeraryColumnFx->addRef(); m_zeraryColumnFx->setColumn(this); m_zeraryFxLevel->addRef(); From 34075514d227751e7de8b9426e2df06bfc4888dd Mon Sep 17 00:00:00 2001 From: justburner Date: Mon, 29 Nov 2021 09:57:58 +0000 Subject: [PATCH 07/18] Improvements to Audio Recording --- toonz/sources/toonz/audiorecordingpopup.cpp | 465 +++++++++++++++----- toonz/sources/toonz/audiorecordingpopup.h | 48 +- 2 files changed, 387 insertions(+), 126 deletions(-) diff --git a/toonz/sources/toonz/audiorecordingpopup.cpp b/toonz/sources/toonz/audiorecordingpopup.cpp index e3362728..5bfc18fa 100644 --- a/toonz/sources/toonz/audiorecordingpopup.cpp +++ b/toonz/sources/toonz/audiorecordingpopup.cpp @@ -66,29 +66,47 @@ AudioRecordingPopup::AudioRecordingPopup() m_saveButton = new QPushButton(tr("Save and Insert")); m_pauseRecordingButton = new QPushButton(this); m_pausePlaybackButton = new QPushButton(this); - // m_refreshDevicesButton = new QPushButton(tr("Refresh")); - m_duration = new QLabel("00:00"); - m_playDuration = new QLabel("00:00"); + m_refreshDevicesButton = new QPushButton(this); + m_duration = new QLabel("00:00.000"); + m_playDuration = new QLabel("00:00.000"); m_deviceListCB = new QComboBox(); m_audioLevelsDisplay = new AudioLevelsDisplay(this); - m_playXSheetCB = new QCheckBox(tr("Sync with Scene"), this); + m_playXSheetCB = new QCheckBox(tr("Sync with XSheet/Timeline"), this); m_timer = new QElapsedTimer(); m_recordedLevels = QMap(); m_oldElapsed = 0; - m_probe = new QAudioProbe; m_player = new QMediaPlayer(this); m_console = FlipConsole::getCurrent(); - m_audioRecorder = new QAudioRecorder; - m_recordButton->setMaximumWidth(25); - m_playButton->setMaximumWidth(25); - m_pauseRecordingButton->setMaximumWidth(25); - m_pausePlaybackButton->setMaximumWidth(25); + m_labelDevice = new QLabel(tr("Device: ")); + m_labelSamplerate = new QLabel(tr("Sample rate: ")); + m_labelSamplefmt = new QLabel(tr("Sample format: ")); + m_comboSamplerate = new QComboBox(); + m_comboSamplefmt = new QComboBox(); + m_comboSamplerate->addItem(tr("8000 Hz"), QVariant::fromValue(8000)); + m_comboSamplerate->addItem(tr("11025 Hz"), QVariant::fromValue(11025)); + m_comboSamplerate->addItem(tr("22050 Hz"), QVariant::fromValue(22050)); + m_comboSamplerate->addItem(tr("44100 Hz"), QVariant::fromValue(44100)); + m_comboSamplerate->addItem(tr("48000 Hz"), QVariant::fromValue(48000)); + m_comboSamplerate->addItem(tr("96000 Hz"), QVariant::fromValue(96000)); + m_comboSamplerate->setCurrentIndex(3); // 44.1KHz + m_comboSamplefmt->addItem(tr("Mono 8-Bits"), QVariant::fromValue(9)); + m_comboSamplefmt->addItem(tr("Stereo 8-Bits"), QVariant::fromValue(10)); + m_comboSamplefmt->addItem(tr("Mono 16-Bits"), QVariant::fromValue(17)); + m_comboSamplefmt->addItem(tr("Stereo 16-Bits"), QVariant::fromValue(18)); + m_comboSamplefmt->setCurrentIndex(2); // Mono 16-Bits + + m_recordButton->setMaximumWidth(32); + m_playButton->setMaximumWidth(32); + m_pauseRecordingButton->setMaximumWidth(32); + m_pausePlaybackButton->setMaximumWidth(32); + m_refreshDevicesButton->setMaximumWidth(25); QString playDisabled = QString(":Resources/play_disabled.svg"); QString pauseDisabled = QString(":Resources/pause_disabled.svg"); QString stopDisabled = QString(":Resources/stop_disabled.svg"); QString recordDisabled = QString(":Resources/record_disabled.svg"); + QString refreshDisabled = QString(":Resources/repeat_icon.svg"); m_pauseIcon = createQIcon("pause"); m_pauseIcon.addFile(pauseDisabled, QSize(), QIcon::Disabled); @@ -98,6 +116,9 @@ AudioRecordingPopup::AudioRecordingPopup() m_recordIcon.addFile(recordDisabled, QSize(), QIcon::Disabled); m_stopIcon = createQIcon("stop"); m_stopIcon.addFile(stopDisabled, QSize(), QIcon::Disabled); + m_refreshIcon = createQIcon("repeat"); + m_refreshIcon.addFile(refreshDisabled, QSize(), QIcon::Disabled); + m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setIconSize(QSize(17, 17)); m_playButton->setIcon(m_playIcon); @@ -106,12 +127,32 @@ AudioRecordingPopup::AudioRecordingPopup() m_recordButton->setIconSize(QSize(17, 17)); m_pausePlaybackButton->setIcon(m_pauseIcon); m_pausePlaybackButton->setIconSize(QSize(17, 17)); + m_refreshDevicesButton->setIcon(m_refreshIcon); + m_refreshDevicesButton->setIconSize(QSize(17, 17)); - QStringList inputs = m_audioRecorder->audioInputs(); - m_deviceListCB->addItems(inputs); - QString selectedInput = m_audioRecorder->defaultAudioInput(); - m_deviceListCB->setCurrentText(selectedInput); - m_audioRecorder->setAudioInput(selectedInput); + // Enumerate devices and initialize default device + enumerateAudioDevices(""); + QAudioDeviceInfo m_audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice(); + QAudioFormat format; + format.setSampleRate(44100); + format.setChannelCount(1); + format.setSampleSize(16); + format.setSampleType(QAudioFormat::SignedInt); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setCodec("audio/pcm"); + if (!m_audioDeviceInfo.isFormatSupported(format)) { + format = m_audioDeviceInfo.nearestFormat(format); + } + m_audioInput = new QAudioInput(m_audioDeviceInfo, format); + m_audioWriterWAV = new AudioWriterWAV(format); + + // Tool tips to provide additional info to the user + m_deviceListCB->setToolTip(tr("Audio input device to record")); + m_comboSamplerate->setToolTip(tr("Number of samples per second, 44.1KHz = CD Quality")); + m_comboSamplefmt->setToolTip(tr("Number of channels and bits per sample, 16-bits recommended")); + m_playXSheetCB->setToolTip(tr("Play animation from current frame while recording/playback")); + m_saveButton->setToolTip(tr("Save recording and insert into new column")); + m_refreshDevicesButton->setToolTip(tr("Refresh list of connected audio input devices")); m_topLayout->setMargin(5); m_topLayout->setSpacing(8); @@ -124,11 +165,19 @@ AudioRecordingPopup::AudioRecordingPopup() recordGridLay->setHorizontalSpacing(2); recordGridLay->setVerticalSpacing(3); { - recordGridLay->addWidget(m_deviceListCB, 0, 0, 1, 4, Qt::AlignCenter); - // recordGridLay->addWidget(m_refreshDevicesButton, 0, 3, Qt::AlignLeft); - recordGridLay->addWidget(new QLabel(tr(" ")), 1, 0, Qt::AlignCenter); - recordGridLay->addWidget(m_audioLevelsDisplay, 2, 0, 1, 4, + recordGridLay->addWidget(m_labelDevice, 0, 0, 1, 2, Qt::AlignRight); + recordGridLay->addWidget(m_deviceListCB, 0, 2, 1, 2, Qt::AlignLeft); + + recordGridLay->addWidget(m_labelSamplerate, 1, 0, 1, 2, Qt::AlignRight); + recordGridLay->addWidget(m_comboSamplerate, 1, 2, 1, 1, Qt::AlignLeft); + recordGridLay->addWidget(m_refreshDevicesButton, 1, 3, Qt::AlignRight); + + recordGridLay->addWidget(m_labelSamplefmt, 2, 0, 1, 2, Qt::AlignRight); + recordGridLay->addWidget(m_comboSamplefmt, 2, 2, 1, 2, Qt::AlignLeft); + + recordGridLay->addWidget(m_audioLevelsDisplay, 3, 0, 1, 4, Qt::AlignCenter); + recordGridLay->addWidget(m_playXSheetCB, 4, 0, 1, 5, Qt::AlignCenter); QHBoxLayout *recordLay = new QHBoxLayout(); recordLay->setSpacing(4); recordLay->setContentsMargins(0, 0, 0, 0); @@ -139,7 +188,7 @@ AudioRecordingPopup::AudioRecordingPopup() recordLay->addWidget(m_duration); recordLay->addStretch(); } - recordGridLay->addLayout(recordLay, 3, 0, 1, 4, Qt::AlignCenter); + recordGridLay->addLayout(recordLay, 5, 0, 1, 4, Qt::AlignCenter); QHBoxLayout *playLay = new QHBoxLayout(); playLay->setSpacing(4); playLay->setContentsMargins(0, 0, 0, 0); @@ -150,11 +199,9 @@ AudioRecordingPopup::AudioRecordingPopup() playLay->addWidget(m_playDuration); playLay->addStretch(); } - recordGridLay->addLayout(playLay, 4, 0, 1, 4, Qt::AlignCenter); - recordGridLay->addWidget(new QLabel(tr(" ")), 5, 0, Qt::AlignCenter); - recordGridLay->addWidget(m_saveButton, 6, 0, 1, 4, - Qt::AlignCenter | Qt::AlignVCenter); - recordGridLay->addWidget(m_playXSheetCB, 7, 0, 1, 4, + recordGridLay->addLayout(playLay, 6, 0, 1, 4, Qt::AlignCenter); + recordGridLay->addWidget(new QLabel(tr(" ")), 7, 0, Qt::AlignCenter); + recordGridLay->addWidget(m_saveButton, 8, 0, 1, 4, Qt::AlignCenter | Qt::AlignVCenter); } recordGridLay->setColumnStretch(0, 0); @@ -172,23 +219,6 @@ AudioRecordingPopup::AudioRecordingPopup() m_playXSheetCB->setChecked(true); - m_probe->setSource(m_audioRecorder); - QAudioEncoderSettings audioSettings; - audioSettings.setCodec("audio/PCM"); - // setting the sample rate to some value (like 44100) - // may cause divide-by-zero crash in QAudioDeviceInfo::nearestFormat() - // so here we set the value to -1, as the documentation says; - // "A value of -1 indicates the encoder should make an optimal choice" - audioSettings.setSampleRate(-1); - audioSettings.setChannelCount(1); - audioSettings.setBitRate(16); - audioSettings.setEncodingMode(QMultimedia::ConstantBitRateEncoding); - audioSettings.setQuality(QMultimedia::HighQuality); - m_audioRecorder->setContainerFormat("wav"); - m_audioRecorder->setEncodingSettings(audioSettings); - - connect(m_probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, - SLOT(processBuffer(QAudioBuffer))); connect(m_playXSheetCB, SIGNAL(stateChanged(int)), this, SLOT(onPlayXSheetCBChanged(int))); connect(m_saveButton, SIGNAL(clicked()), this, SLOT(onSaveButtonPressed())); @@ -199,16 +229,18 @@ AudioRecordingPopup::AudioRecordingPopup() SLOT(onPauseRecordingButtonPressed())); connect(m_pausePlaybackButton, SIGNAL(clicked()), this, SLOT(onPausePlaybackButtonPressed())); - connect(m_audioRecorder, SIGNAL(durationChanged(qint64)), this, + connect(m_audioWriterWAV, SIGNAL(update(qint64)), this, SLOT(updateRecordDuration(qint64))); - connect(m_console, SIGNAL(playStateChanged(bool)), this, + if (m_console) connect(m_console, SIGNAL(playStateChanged(bool)), this, SLOT(onPlayStateChanged(bool))); connect(m_deviceListCB, SIGNAL(currentTextChanged(const QString)), this, SLOT(onInputDeviceChanged())); - // connect(m_refreshDevicesButton, SIGNAL(clicked()), this, - // SLOT(onRefreshButtonPressed())); - // connect(m_audioRecorder, SIGNAL(availableAudioInputsChanged()), this, - // SLOT(onRefreshButtonPressed())); + connect(m_refreshDevicesButton, SIGNAL(clicked()), this, + SLOT(onRefreshButtonPressed())); + connect(m_comboSamplerate, SIGNAL(currentTextChanged(const QString)), this, + SLOT(onAudioSettingChanged())); + connect(m_comboSamplefmt, SIGNAL(currentTextChanged(const QString)), this, + SLOT(onAudioSettingChanged())); } //----------------------------------------------------------------------------- @@ -218,11 +250,24 @@ AudioRecordingPopup::~AudioRecordingPopup() {} //----------------------------------------------------------------------------- void AudioRecordingPopup::onRecordButtonPressed() { - if (m_audioRecorder->state() == QAudioRecorder::StoppedState) { - if (m_audioRecorder->status() == QMediaRecorder::UnavailableStatus) { +#if QT_VERSION >= 0x051000 + if (m_audioInput->state() == QAudio::InterruptedState) { + DVGui::warning( + tr("The microphone is not available: " + "\nPlease select a different device or check the microphone.")); + return; + } else if (m_audioInput->state() == QAudio::StoppedState) { + if (!m_console) { + DVGui::warning( + tr("Record failed: " + "\nMake sure there's XSheet or Timeline in the room.")); +#else + if (m_audioInput->state() == QAudio::StoppedState) { + if (!m_console) { DVGui::warning( tr("The microphone is not available: " "\nPlease select a different device or check the microphone.")); +#endif return; } // clear the player in case the file is open there @@ -236,8 +281,6 @@ void AudioRecordingPopup::onRecordButtonPressed() { // (rarely) // could cause a crash. I think OT tried to import the level before the // final file was fully copied to the new location - m_audioRecorder->setOutputLocation( - QUrl::fromLocalFile(m_filePath.getQString())); if (TSystem::doesExistFileOrLevel(m_filePath)) { TSystem::removeFileOrLevel(m_filePath); } @@ -246,15 +289,21 @@ void AudioRecordingPopup::onRecordButtonPressed() { m_playButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true); m_pauseRecordingButton->setEnabled(true); + m_deviceListCB->setDisabled(true); + m_refreshDevicesButton->setDisabled(true); + m_comboSamplerate->setDisabled(true); + m_comboSamplefmt->setDisabled(true); m_recordedLevels.clear(); m_oldElapsed = 0; m_pausedTime = 0; m_startPause = 0; m_endPause = 0; m_stoppedAtEnd = false; - m_playDuration->setText("00:00"); + m_playDuration->setText("00:00.000"); m_timer->restart(); - m_audioRecorder->record(); + m_audioWriterWAV->restart(m_audioInput->format()); + m_audioWriterWAV->start(); + m_audioInput->start(m_audioWriterWAV); // this sometimes sets to one frame off, so + 1. m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1; if (m_syncPlayback && !m_isPlaying) { @@ -264,13 +313,20 @@ void AudioRecordingPopup::onRecordButtonPressed() { } } else { - m_audioRecorder->stop(); - m_audioLevelsDisplay->setLevel(0); + m_audioInput->stop(); + m_audioWriterWAV->stop(); + if (m_audioWriterWAV->save(m_filePath.getQString())) { + m_saveButton->setEnabled(true); + m_playButton->setEnabled(true); + } + m_audioLevelsDisplay->setLevel(-1); m_recordButton->setIcon(m_recordIcon); - m_saveButton->setEnabled(true); - m_playButton->setEnabled(true); m_pauseRecordingButton->setDisabled(true); m_pauseRecordingButton->setIcon(m_pauseIcon); + m_deviceListCB->setEnabled(true); + m_refreshDevicesButton->setEnabled(true); + m_comboSamplerate->setEnabled(true); + m_comboSamplefmt->setEnabled(true); if (m_syncPlayback) { if (m_isPlaying) { m_console->pressButton(FlipConsole::ePause); @@ -285,15 +341,18 @@ void AudioRecordingPopup::onRecordButtonPressed() { //----------------------------------------------------------------------------- void AudioRecordingPopup::updateRecordDuration(qint64 duration) { - // this is only called every second or so - sometimes duration ~= 950 - // this gives some padding so it doesn't take two seconds to show one second - // has passed - if (duration % 1000 > 850) duration += 150; int minutes = duration / 60000; int seconds = (duration / 1000) % 60; + int milis = duration % 1000; QString strMinutes = QString::number(minutes).rightJustified(2, '0'); QString strSeconds = QString::number(seconds).rightJustified(2, '0'); - m_duration->setText(strMinutes + ":" + strSeconds); + QString strMilis = QString::number(milis).rightJustified(3, '0'); + m_duration->setText(strMinutes + ":" + strSeconds + "." + strMilis); + + // Show and record amplitude + qreal level = m_audioWriterWAV->level(); + m_audioLevelsDisplay->setLevel(level); + m_recordedLevels[duration / 20] = level; } //----------------------------------------------------------------------------- @@ -301,9 +360,11 @@ void AudioRecordingPopup::updateRecordDuration(qint64 duration) { void AudioRecordingPopup::updatePlaybackDuration(qint64 duration) { int minutes = duration / 60000; int seconds = (duration / 1000) % 60; + int milis = duration % 1000; QString strMinutes = QString::number(minutes).rightJustified(2, '0'); QString strSeconds = QString::number(seconds).rightJustified(2, '0'); - m_playDuration->setText(strMinutes + ":" + strSeconds); + QString strMilis = QString::number(milis).rightJustified(3, '0'); + m_playDuration->setText(strMinutes + ":" + strSeconds + "." + strMilis); // the qmediaplayer probe doesn't work on all platforms, so we fake it by // using @@ -328,6 +389,10 @@ void AudioRecordingPopup::onPlayButtonPressed() { m_recordButton->setDisabled(true); m_saveButton->setDisabled(true); m_pausePlaybackButton->setEnabled(true); + m_deviceListCB->setDisabled(true); + m_refreshDevicesButton->setDisabled(true); + m_comboSamplerate->setDisabled(true); + m_comboSamplefmt->setDisabled(true); m_stoppedAtEnd = false; m_player->play(); // this sometimes sets to one frame off, so + 1. @@ -343,25 +408,29 @@ void AudioRecordingPopup::onPlayButtonPressed() { m_playButton->setIcon(m_playIcon); m_pausePlaybackButton->setDisabled(true); m_pausePlaybackButton->setIcon(m_pauseIcon); + m_deviceListCB->setEnabled(true); + m_refreshDevicesButton->setEnabled(true); + m_comboSamplerate->setEnabled(true); + m_comboSamplefmt->setEnabled(true); } } //----------------------------------------------------------------------------- void AudioRecordingPopup::onPauseRecordingButtonPressed() { - if (m_audioRecorder->state() == QAudioRecorder::StoppedState) { + if (m_audioInput->state() == QAudio::StoppedState) { return; - } else if (m_audioRecorder->state() == QAudioRecorder::PausedState) { + } else if (m_audioInput->state() == QAudio::SuspendedState) { m_endPause = m_timer->elapsed(); m_pausedTime += m_endPause - m_startPause; - m_audioRecorder->record(); + m_audioInput->resume(); m_pauseRecordingButton->setIcon(m_pauseIcon); if (m_syncPlayback && !m_isPlaying && !m_stoppedAtEnd) { m_console->pressButton(FlipConsole::ePlay); m_isPlaying = true; } } else { - m_audioRecorder->pause(); + m_audioInput->suspend(); m_pauseRecordingButton->setIcon(m_recordIcon); m_startPause = m_timer->elapsed(); if (m_syncPlayback && m_isPlaying) { @@ -398,7 +467,7 @@ void AudioRecordingPopup::onPausePlaybackButtonPressed() { void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) { // stopping can happen through the stop button or the file ending if (state == QMediaPlayer::StoppedState) { - m_audioLevelsDisplay->setLevel(0); + m_audioLevelsDisplay->setLevel(-1); if (m_syncPlayback) { if (m_isPlaying) { m_console->pressButton(FlipConsole::ePause); @@ -410,6 +479,10 @@ void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) { m_pausePlaybackButton->setIcon(m_pauseIcon); m_pausePlaybackButton->setDisabled(true); m_recordButton->setEnabled(true); + m_deviceListCB->setEnabled(true); + m_refreshDevicesButton->setEnabled(true); + m_comboSamplerate->setEnabled(true); + m_comboSamplefmt->setEnabled(true); m_saveButton->setEnabled(true); m_isPlaying = false; } @@ -426,36 +499,36 @@ void AudioRecordingPopup::onPlayXSheetCBChanged(int status) { //----------------------------------------------------------------------------- -// Refresh isn't working right now, but I'm leaving the code in case a future -// change -// makes it work +void AudioRecordingPopup::onRefreshButtonPressed() { + QAudioDeviceInfo m_audioDeviceInfo = + m_deviceListCB->itemData(m_deviceListCB->currentIndex()) + .value(); -// void AudioRecordingPopup::onRefreshButtonPressed() { -// m_deviceListCB->clear(); -// QStringList inputs = m_audioRecorder->audioInputs(); -// int count = inputs.count(); -// m_deviceListCB->addItems(inputs); -// QString selectedInput = m_audioRecorder->defaultAudioInput(); -// m_deviceListCB->setCurrentText(selectedInput); -// -//} + enumerateAudioDevices(m_audioDeviceInfo.deviceName()); +} //----------------------------------------------------------------------------- void AudioRecordingPopup::onInputDeviceChanged() { - m_audioRecorder->setAudioInput(m_deviceListCB->currentText()); + reinitAudioInput(); +} + +//----------------------------------------------------------------------------- + +void AudioRecordingPopup::onAudioSettingChanged() { + reinitAudioInput(); } //----------------------------------------------------------------------------- void AudioRecordingPopup::onSaveButtonPressed() { - if (m_audioRecorder->state() != QAudioRecorder::StoppedState) { - m_audioRecorder->stop(); - m_audioLevelsDisplay->setLevel(0); + if (m_audioInput->state() != QAudio::StoppedState) { + m_audioInput->stop(); + m_audioLevelsDisplay->setLevel(-1); } if (m_player->state() != QMediaPlayer::StoppedState) { m_player->stop(); - m_audioLevelsDisplay->setLevel(0); + m_audioLevelsDisplay->setLevel(-1); } if (!TSystem::doesExistFileOrLevel(m_filePath)) return; @@ -499,31 +572,6 @@ void AudioRecordingPopup::makePaths() { //----------------------------------------------------------------------------- -void AudioRecordingPopup::processBuffer(const QAudioBuffer &buffer) { - // keep from processing too many times - // get 50 signals per second - if (m_timer->elapsed() < m_oldElapsed + 20) return; - m_oldElapsed = m_timer->elapsed() - m_pausedTime; - qint16 value = 0; - - if (!buffer.format().isValid() || - buffer.format().byteOrder() != QAudioFormat::LittleEndian) - return; - - if (buffer.format().codec() != "audio/pcm") return; - - const qint16 *data = buffer.constData(); - qreal maxValue = 0; - qreal tempValue = 0; - for (int i = 0; i < buffer.frameCount(); ++i) { - tempValue = qAbs(qreal(data[i])); - if (tempValue > maxValue) maxValue = tempValue; - } - maxValue /= SHRT_MAX; - m_audioLevelsDisplay->setLevel(maxValue); - m_recordedLevels[m_oldElapsed / 20] = maxValue; -} - void AudioRecordingPopup::onPlayStateChanged(bool playing) { // m_isPlaying = playing; if (!playing && m_isPlaying) m_stoppedAtEnd = true; @@ -545,22 +593,33 @@ void AudioRecordingPopup::resetEverything() { m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true); + m_deviceListCB->setEnabled(true); + m_refreshDevicesButton->setEnabled(true); + m_comboSamplerate->setEnabled(true); + m_comboSamplefmt->setEnabled(true); m_recordedLevels.clear(); - m_duration->setText("00:00"); - m_playDuration->setText("00:00"); - m_audioLevelsDisplay->setLevel(0); + m_duration->setText("00:00.000"); + m_playDuration->setText("00:00.000"); + m_audioLevelsDisplay->setLevel(-1); + if (!m_console) { + m_console = FlipConsole::getCurrent(); + if (m_console) + connect(m_console, SIGNAL(playStateChanged(bool)), this, + SLOT(onPlayStateChanged(bool))); + } } //----------------------------------------------------------------------------- void AudioRecordingPopup::hideEvent(QHideEvent *event) { - if (m_audioRecorder->state() != QAudioRecorder::StoppedState) { - m_audioRecorder->stop(); + if (m_audioInput->state() != QAudio::StoppedState) { + m_audioInput->stop(); } if (m_player->state() != QMediaPlayer::StoppedState) { m_player->stop(); } // make sure the file is freed before deleting + delete m_player; m_player = new QMediaPlayer(this); // this should only remove files that haven't been used in the scene // make paths checks to only create path names that don't exist yet. @@ -569,6 +628,172 @@ void AudioRecordingPopup::hideEvent(QHideEvent *event) { } } +//----------------------------------------------------------------------------- + +void AudioRecordingPopup::enumerateAudioDevices(const QString &selectedDeviceName) { + const QAudioDeviceInfo &defaultDeviceInfo = + QAudioDeviceInfo::defaultInputDevice(); + + m_blockAudioSettings = true; + m_deviceListCB->clear(); + m_deviceListCB->addItem(defaultDeviceInfo.deviceName(), + QVariant::fromValue(defaultDeviceInfo)); + + for (auto &deviceInfo : + QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) { + if (deviceInfo != defaultDeviceInfo && + m_deviceListCB->findText(deviceInfo.deviceName()) == -1) { + m_deviceListCB->addItem(deviceInfo.deviceName(), + QVariant::fromValue(deviceInfo)); + } + } + + int deviceIndex = m_deviceListCB->findText(selectedDeviceName); + if (deviceIndex != -1) m_deviceListCB->setCurrentIndex(deviceIndex); + m_blockAudioSettings = false; +} + +//----------------------------------------------------------------------------- + +void AudioRecordingPopup::reinitAudioInput() { + if (m_blockAudioSettings) return; + + QAudioDeviceInfo m_audioDeviceInfo = + m_deviceListCB->itemData(m_deviceListCB->currentIndex()) + .value(); + int samplerate = + m_comboSamplerate->itemData(m_comboSamplerate->currentIndex()) + .value(); + int sampletype = + m_comboSamplefmt->itemData(m_comboSamplefmt->currentIndex()).value(); + int bitdepth = sampletype & 56; + int channels = sampletype & 7; + + QAudioFormat format; + format.setSampleRate(samplerate); + format.setChannelCount(channels); + format.setSampleSize(bitdepth); + format.setSampleType(bitdepth == 8 ? QAudioFormat::UnSignedInt + : QAudioFormat::SignedInt); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setCodec("audio/pcm"); + if (!m_audioDeviceInfo.isFormatSupported(format)) { + DVGui::warning(tr( + "Audio format unsupported:\nNearest format will be internally used.")); + format = m_audioDeviceInfo.nearestFormat(format); + } + + // Recreate input + delete m_audioInput; + m_audioInput = new QAudioInput(m_audioDeviceInfo, format); + m_audioWriterWAV->restart(format); +} + +//----------------------------------------------------------------------------- +// AudioWriterWAV Class +//----------------------------------------------------------------------------- +// IODevice to write standard WAV files, performs peak level calc +// +// 8-bits audio must be unsigned +// 16-bits audio must be signed +// 32-bits isn't supported + +AudioWriterWAV::AudioWriterWAV(const QAudioFormat &format) + : m_level(0.0), m_maxAmp(0.0) { + restart(format); +} + +bool AudioWriterWAV::restart(const QAudioFormat &format) { + m_format = format; + if (m_format.sampleSize() == 8) { + m_rbytesms = 1000.0 / (m_format.sampleRate() * m_format.channelCount()); + m_maxAmp = 127.0; + } else if (m_format.sampleSize() == 16) { + m_rbytesms = 500.0 / (m_format.sampleRate() * m_format.channelCount()); + m_maxAmp = 32767.0; + } else { + // 32-bits isn't supported + m_rbytesms = 250.0 / (m_format.sampleRate() * m_format.channelCount()); + m_maxAmp = 1.0; + } + m_barray.clear(); + return this->reset(); +} + +void AudioWriterWAV::start() { + open(QIODevice::WriteOnly); +} + +void AudioWriterWAV::stop() { + close(); +} + +qint64 AudioWriterWAV::readData(char *data, qint64 maxlen) { + Q_UNUSED(data) + Q_UNUSED(maxlen) + return 0; +} + +qint64 AudioWriterWAV::writeData(const char *data, qint64 len) { + qreal tmp, peak = 0.0; + + // Measure peak + if (m_format.sampleSize() == 8) { + const quint8 *sdata = (const quint8 *)data; + int slen = len; + for (int i = 0; i < slen; ++i) { + tmp = qAbs(qreal(sdata[i]) - 128.0); + if (tmp > peak) peak = tmp; + } + } else if (m_format.sampleSize() == 16) { + const qint16 *sdata = (const qint16 *)data; + int slen = len / 2; + for (int i = 0; i < slen; ++i) { + tmp = qAbs(qreal(sdata[i])); + if (tmp > peak) peak = tmp; + } + } else { + // 32-bits isn't supported + peak = -1.0; + } + m_level = peak / m_maxAmp; + + // Write to buffer + m_barray.append(data, len); + + // Emit an update + emit update(m_barray.size() * m_rbytesms); + return len; +} + +bool AudioWriterWAV::save(const QString &filename) +{ + QFile file; + quint16 channels = m_format.channelCount(); + quint32 samplerate = m_format.sampleRate(); + quint16 bitrate = m_format.sampleSize(); + file.setFileName(filename); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + DVGui::warning(tr( + "Failed to save WAV file:\nMake sure you have folder permissions.")); + return false; + } + QDataStream out(&file); + out.setByteOrder(QDataStream::LittleEndian); + out.writeRawData("RIFF", 4); + out << (quint32)(m_barray.size() + 44); + out.writeRawData("WAVEfmt ", 8); + out << (quint32)16 << (quint16)1; + out << channels << samplerate; + out << quint32(samplerate * channels * bitrate / 8); + out << quint16(channels * bitrate / 8); + out << bitrate; + out.writeRawData("data", 4); + out << (quint32)m_barray.size(); + out.writeRawData(m_barray.constData(), m_barray.size()); + return true; +} + //----------------------------------------------------------------------------- // AudioLevelsDisplay Class //----------------------------------------------------------------------------- @@ -591,11 +816,11 @@ void AudioLevelsDisplay::paintEvent(QPaintEvent *event) { QPainter painter(this); QColor color; - if (m_level < 0.5) { + if (m_level < 0.0) { + return; // draw nothing... + } else if (m_level < 0.5) { color = Qt::green; - } - - else if (m_level < 0.75) { + } else if (m_level < 0.75) { color = QColor(204, 205, 0); // yellow } else if (m_level < 0.95) { color = QColor(255, 115, 0); // orange diff --git a/toonz/sources/toonz/audiorecordingpopup.h b/toonz/sources/toonz/audiorecordingpopup.h index d7c50887..c64f4285 100644 --- a/toonz/sources/toonz/audiorecordingpopup.h +++ b/toonz/sources/toonz/audiorecordingpopup.h @@ -15,7 +15,6 @@ class QComboBox; class QCheckBox; class QPushButton; -class QAudioRecorder; class QLabel; class AudioLevelsDisplay; class FlipConsole; @@ -23,6 +22,7 @@ class QAudioProbe; class QAudioBuffer; class QMediaPlayer; class QElapsedTimer; +class AudioWriterWAV; //============================================================================= // AudioRecordingPopup @@ -31,13 +31,13 @@ class QElapsedTimer; class AudioRecordingPopup : public DVGui::Dialog { Q_OBJECT - QString m_deviceName; QPushButton - *m_recordButton, // *m_refreshDevicesButton, -refresh not working for now + *m_recordButton, *m_refreshDevicesButton, *m_playButton, *m_pauseRecordingButton, *m_pausePlaybackButton, *m_saveButton; QComboBox *m_deviceListCB; - QAudioRecorder *m_audioRecorder; + QAudioInput *m_audioInput; + AudioWriterWAV *m_audioWriterWAV; QLabel *m_duration, *m_playDuration; QCheckBox *m_playXSheetCB; int m_currentFrame; @@ -56,7 +56,11 @@ class AudioRecordingPopup : public DVGui::Dialog { QIcon m_pauseIcon; QIcon m_recordIcon; QIcon m_stopIcon; + QIcon m_refreshIcon; bool m_isPlaying, m_syncPlayback, m_stoppedAtEnd; + QLabel *m_labelDevice, *m_labelSamplerate, *m_labelSamplefmt; + QComboBox *m_comboSamplerate, *m_comboSamplefmt; + bool m_blockAudioSettings; public: AudioRecordingPopup(); @@ -67,6 +71,8 @@ protected: void hideEvent(QHideEvent *event); void makePaths(); void resetEverything(); + void enumerateAudioDevices(const QString &deviceName); + void reinitAudioInput(); private slots: void onRecordButtonPressed(); @@ -76,12 +82,42 @@ private slots: void onSaveButtonPressed(); void onPauseRecordingButtonPressed(); void onPausePlaybackButtonPressed(); - void processBuffer(const QAudioBuffer &buffer); void onPlayStateChanged(bool playing); void onPlayXSheetCBChanged(int status); void onMediaStateChanged(QMediaPlayer::State state); void onInputDeviceChanged(); - // void onRefreshButtonPressed(); + void onRefreshButtonPressed(); + void onAudioSettingChanged(); +}; + +//============================================================================= +// AudioWriterWAV +//----------------------------------------------------------------------------- + +class AudioWriterWAV : public QIODevice { + Q_OBJECT +public: + AudioWriterWAV(const QAudioFormat &format); + bool restart(const QAudioFormat &format); + + void start(); + void stop(); + bool save(const QString &filename); + + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; + + qreal level() const { return m_level; } + +private: + QByteArray m_barray; + QAudioFormat m_format; + qreal m_rbytesms; + qreal m_maxAmp; + qreal m_level; + +signals: + void update(qint64 duration); }; //============================================================================= From a1b7482a5b15e9a062035d43352999886d5d5335 Mon Sep 17 00:00:00 2001 From: justburner Date: Mon, 29 Nov 2021 17:50:57 +0000 Subject: [PATCH 08/18] Improvements to Audio Recording II: Free up memory in SPAAACE! --- toonz/sources/toonz/audiorecordingpopup.cpp | 8 ++++++-- toonz/sources/toonz/audiorecordingpopup.h | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/toonz/sources/toonz/audiorecordingpopup.cpp b/toonz/sources/toonz/audiorecordingpopup.cpp index 5bfc18fa..5e5e07f0 100644 --- a/toonz/sources/toonz/audiorecordingpopup.cpp +++ b/toonz/sources/toonz/audiorecordingpopup.cpp @@ -74,7 +74,6 @@ AudioRecordingPopup::AudioRecordingPopup() m_playXSheetCB = new QCheckBox(tr("Sync with XSheet/Timeline"), this); m_timer = new QElapsedTimer(); m_recordedLevels = QMap(); - m_oldElapsed = 0; m_player = new QMediaPlayer(this); m_console = FlipConsole::getCurrent(); @@ -294,7 +293,6 @@ void AudioRecordingPopup::onRecordButtonPressed() { m_comboSamplerate->setDisabled(true); m_comboSamplefmt->setDisabled(true); m_recordedLevels.clear(); - m_oldElapsed = 0; m_pausedTime = 0; m_startPause = 0; m_endPause = 0; @@ -319,6 +317,7 @@ void AudioRecordingPopup::onRecordButtonPressed() { m_saveButton->setEnabled(true); m_playButton->setEnabled(true); } + m_audioWriterWAV->freeup(); m_audioLevelsDisplay->setLevel(-1); m_recordButton->setIcon(m_recordIcon); m_pauseRecordingButton->setDisabled(true); @@ -626,6 +625,9 @@ void AudioRecordingPopup::hideEvent(QHideEvent *event) { if (TSystem::doesExistFileOrLevel(TFilePath(m_filePath.getQString()))) { TSystem::removeFileOrLevel(TFilePath(m_filePath.getQString())); } + // Free up memory used in recording + m_recordedLevels.clear(); + m_audioWriterWAV->freeup(); } //----------------------------------------------------------------------------- @@ -794,6 +796,8 @@ bool AudioWriterWAV::save(const QString &filename) return true; } +void AudioWriterWAV::freeup() { m_barray.clear(); } + //----------------------------------------------------------------------------- // AudioLevelsDisplay Class //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/audiorecordingpopup.h b/toonz/sources/toonz/audiorecordingpopup.h index c64f4285..ec3e3f88 100644 --- a/toonz/sources/toonz/audiorecordingpopup.h +++ b/toonz/sources/toonz/audiorecordingpopup.h @@ -18,7 +18,6 @@ class QPushButton; class QLabel; class AudioLevelsDisplay; class FlipConsole; -class QAudioProbe; class QAudioBuffer; class QMediaPlayer; class QElapsedTimer; @@ -42,13 +41,11 @@ class AudioRecordingPopup : public DVGui::Dialog { QCheckBox *m_playXSheetCB; int m_currentFrame; AudioLevelsDisplay *m_audioLevelsDisplay; - QAudioProbe *m_probe; QMediaPlayer *m_player; TFilePath m_filePath; FlipConsole *m_console; QElapsedTimer *m_timer; QMap m_recordedLevels; - qint64 m_oldElapsed; qint64 m_startPause = 0; qint64 m_endPause = 0; qint64 m_pausedTime = 0; @@ -103,6 +100,7 @@ public: void start(); void stop(); bool save(const QString &filename); + void freeup(); qint64 readData(char *data, qint64 maxlen) override; qint64 writeData(const char *data, qint64 len) override; From 290ff0b0d3bf8551ebcd068e0e180aa03c09ed2d Mon Sep 17 00:00:00 2001 From: justburner Date: Tue, 30 Nov 2021 10:29:32 +0000 Subject: [PATCH 09/18] Improvements to Audio Recording III: Peak menace! --- toonz/sources/toonz/audiorecordingpopup.cpp | 190 +++++++++++++------- toonz/sources/toonz/audiorecordingpopup.h | 19 +- 2 files changed, 140 insertions(+), 69 deletions(-) diff --git a/toonz/sources/toonz/audiorecordingpopup.cpp b/toonz/sources/toonz/audiorecordingpopup.cpp index 5e5e07f0..892b0553 100644 --- a/toonz/sources/toonz/audiorecordingpopup.cpp +++ b/toonz/sources/toonz/audiorecordingpopup.cpp @@ -283,6 +283,16 @@ void AudioRecordingPopup::onRecordButtonPressed() { if (TSystem::doesExistFileOrLevel(m_filePath)) { TSystem::removeFileOrLevel(m_filePath); } + // The audio writer support either writing to buffer or directly to disk + // each method have their own pros and cons + // For now using false to mimic previous QAudioRecorder behaviour + m_audioWriterWAV->restart(m_audioInput->format()); + if (!m_audioWriterWAV->start(m_filePath.getQString(), false)) { + DVGui::warning( + tr("Failed to save WAV file:\nMake sure you have write permissions " + "in folder.")); + return; + } m_recordButton->setIcon(m_stopIcon); m_saveButton->setDisabled(true); m_playButton->setDisabled(true); @@ -299,8 +309,6 @@ void AudioRecordingPopup::onRecordButtonPressed() { m_stoppedAtEnd = false; m_playDuration->setText("00:00.000"); m_timer->restart(); - m_audioWriterWAV->restart(m_audioInput->format()); - m_audioWriterWAV->start(); m_audioInput->start(m_audioWriterWAV); // this sometimes sets to one frame off, so + 1. m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1; @@ -312,13 +320,12 @@ void AudioRecordingPopup::onRecordButtonPressed() { } else { m_audioInput->stop(); - m_audioWriterWAV->stop(); - if (m_audioWriterWAV->save(m_filePath.getQString())) { + bool success = m_audioWriterWAV->stop(); + if (success) { m_saveButton->setEnabled(true); m_playButton->setEnabled(true); } - m_audioWriterWAV->freeup(); - m_audioLevelsDisplay->setLevel(-1); + m_audioLevelsDisplay->setLevel(-1, -1); m_recordButton->setIcon(m_recordIcon); m_pauseRecordingButton->setDisabled(true); m_pauseRecordingButton->setIcon(m_pauseIcon); @@ -334,6 +341,10 @@ void AudioRecordingPopup::onRecordButtonPressed() { TApp::instance()->getCurrentFrame()->setCurrentFrame(m_currentFrame); } m_isPlaying = false; + if (!success) { + DVGui::warning(tr( + "Failed to save WAV file:\nMake sure you have write permissions in folder.")); + } } } @@ -350,7 +361,8 @@ void AudioRecordingPopup::updateRecordDuration(qint64 duration) { // Show and record amplitude qreal level = m_audioWriterWAV->level(); - m_audioLevelsDisplay->setLevel(level); + qreal peakL = m_audioWriterWAV->peakLevel(); + m_audioLevelsDisplay->setLevel(level, peakL); m_recordedLevels[duration / 20] = level; } @@ -369,7 +381,7 @@ void AudioRecordingPopup::updatePlaybackDuration(qint64 duration) { // using // a map that is made during recording if (m_recordedLevels.contains(duration / 20)) { - m_audioLevelsDisplay->setLevel(m_recordedLevels.value(duration / 20)); + m_audioLevelsDisplay->setLevel(m_recordedLevels.value(duration / 20), -1); } } @@ -466,7 +478,7 @@ void AudioRecordingPopup::onPausePlaybackButtonPressed() { void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) { // stopping can happen through the stop button or the file ending if (state == QMediaPlayer::StoppedState) { - m_audioLevelsDisplay->setLevel(-1); + m_audioLevelsDisplay->setLevel(-1, -1); if (m_syncPlayback) { if (m_isPlaying) { m_console->pressButton(FlipConsole::ePause); @@ -523,11 +535,11 @@ void AudioRecordingPopup::onAudioSettingChanged() { void AudioRecordingPopup::onSaveButtonPressed() { if (m_audioInput->state() != QAudio::StoppedState) { m_audioInput->stop(); - m_audioLevelsDisplay->setLevel(-1); + m_audioLevelsDisplay->setLevel(-1, -1); } if (m_player->state() != QMediaPlayer::StoppedState) { m_player->stop(); - m_audioLevelsDisplay->setLevel(-1); + m_audioLevelsDisplay->setLevel(-1, -1); } if (!TSystem::doesExistFileOrLevel(m_filePath)) return; @@ -599,7 +611,7 @@ void AudioRecordingPopup::resetEverything() { m_recordedLevels.clear(); m_duration->setText("00:00.000"); m_playDuration->setText("00:00.000"); - m_audioLevelsDisplay->setLevel(-1); + m_audioLevelsDisplay->setLevel(-1, -1); if (!m_console) { m_console = FlipConsole::getCurrent(); if (m_console) @@ -613,6 +625,7 @@ void AudioRecordingPopup::resetEverything() { void AudioRecordingPopup::hideEvent(QHideEvent *event) { if (m_audioInput->state() != QAudio::StoppedState) { m_audioInput->stop(); + m_audioWriterWAV->stop(); } if (m_player->state() != QMediaPlayer::StoppedState) { m_player->stop(); @@ -627,7 +640,6 @@ void AudioRecordingPopup::hideEvent(QHideEvent *event) { } // Free up memory used in recording m_recordedLevels.clear(); - m_audioWriterWAV->freeup(); } //----------------------------------------------------------------------------- @@ -701,7 +713,12 @@ void AudioRecordingPopup::reinitAudioInput() { // 32-bits isn't supported AudioWriterWAV::AudioWriterWAV(const QAudioFormat &format) - : m_level(0.0), m_maxAmp(0.0) { + : m_level(0.0) + , m_peakL(0.0) + , m_maxAmp(0.0) + , m_wrRawB(0) + , m_wavFile(NULL) + , m_wavBuff(NULL) { restart(format); } @@ -718,16 +735,79 @@ bool AudioWriterWAV::restart(const QAudioFormat &format) { m_rbytesms = 250.0 / (m_format.sampleRate() * m_format.channelCount()); m_maxAmp = 1.0; } - m_barray.clear(); + m_wrRawB = 0; + m_peakL = 0.0; + if (m_wavBuff) m_wavBuff->clear(); return this->reset(); } -void AudioWriterWAV::start() { - open(QIODevice::WriteOnly); +// Just a tiny define to avoid a magic number +// this is the size of a WAV header with PCM format +#define AWWAV_HEADER_SIZE 44 + +bool AudioWriterWAV::start(const QString &filename, bool useMem) { + open(QIODevice::WriteOnly); + m_filename = filename; + + if (useMem) { + m_wavBuff = new QByteArray(); + } else { + m_wavFile = new QFile(m_filename); + if (!m_wavFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) + return false; + m_wavFile->seek(AWWAV_HEADER_SIZE); // skip header + } + + m_wrRawB = 0; + m_peakL = 0.0; + return true; } -void AudioWriterWAV::stop() { - close(); +bool AudioWriterWAV::stop() { + close(); + + if (m_wavBuff) { + // Using memory + QFile file; + file.setFileName(m_filename); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; + writeWAVHeader(file); + file.write(m_wavBuff->constData(), m_wavBuff->size()); + delete m_wavBuff; + m_wavBuff = NULL; + } else { + // Using disk directly + writeWAVHeader(*m_wavFile); + m_wavFile->close(); + delete m_wavFile; + m_wavFile = NULL; + } + + m_wrRawB = 0; + m_peakL = 0.0; + return true; +} + +void AudioWriterWAV::writeWAVHeader(QFile &file) { + quint16 channels = m_format.channelCount(); + quint32 samplerate = m_format.sampleRate(); + quint16 bitrate = m_format.sampleSize(); + + qint64 pos = file.pos(); + file.seek(0); + + QDataStream out(&file); + out.setByteOrder(QDataStream::LittleEndian); + out.writeRawData("RIFF", 4); + out << (quint32)(m_wrRawB + AWWAV_HEADER_SIZE); + out.writeRawData("WAVEfmt ", 8); + out << (quint32)16 << (quint16)1; // magic numbers! + out << channels << samplerate; + out << quint32(samplerate * channels * bitrate / 8); + out << quint16(channels * bitrate / 8); + out << bitrate; + out.writeRawData("data", 4); + out << (quint32)m_wrRawB; } qint64 AudioWriterWAV::readData(char *data, qint64 maxlen) { @@ -737,80 +817,57 @@ qint64 AudioWriterWAV::readData(char *data, qint64 maxlen) { } qint64 AudioWriterWAV::writeData(const char *data, qint64 len) { - qreal tmp, peak = 0.0; + int tmp, peak = 0.0; // Measure peak if (m_format.sampleSize() == 8) { const quint8 *sdata = (const quint8 *)data; int slen = len; for (int i = 0; i < slen; ++i) { - tmp = qAbs(qreal(sdata[i]) - 128.0); + tmp = qAbs(sdata[i] - 128); if (tmp > peak) peak = tmp; } } else if (m_format.sampleSize() == 16) { const qint16 *sdata = (const qint16 *)data; int slen = len / 2; for (int i = 0; i < slen; ++i) { - tmp = qAbs(qreal(sdata[i])); + tmp = qAbs(sdata[i]); if (tmp > peak) peak = tmp; } } else { // 32-bits isn't supported - peak = -1.0; + peak = -1; } - m_level = peak / m_maxAmp; + m_level = qreal(peak) / m_maxAmp; + if (m_level > m_peakL) m_peakL = m_level; - // Write to buffer - m_barray.append(data, len); + // Write to memory or disk + if (m_wavBuff) { + m_wavBuff->append(data, len); + } else { + m_wavFile->write(data, len); + } + m_wrRawB += len; // Emit an update - emit update(m_barray.size() * m_rbytesms); + emit update(qreal(m_wrRawB) * m_rbytesms); return len; } -bool AudioWriterWAV::save(const QString &filename) -{ - QFile file; - quint16 channels = m_format.channelCount(); - quint32 samplerate = m_format.sampleRate(); - quint16 bitrate = m_format.sampleSize(); - file.setFileName(filename); - if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - DVGui::warning(tr( - "Failed to save WAV file:\nMake sure you have folder permissions.")); - return false; - } - QDataStream out(&file); - out.setByteOrder(QDataStream::LittleEndian); - out.writeRawData("RIFF", 4); - out << (quint32)(m_barray.size() + 44); - out.writeRawData("WAVEfmt ", 8); - out << (quint32)16 << (quint16)1; - out << channels << samplerate; - out << quint32(samplerate * channels * bitrate / 8); - out << quint16(channels * bitrate / 8); - out << bitrate; - out.writeRawData("data", 4); - out << (quint32)m_barray.size(); - out.writeRawData(m_barray.constData(), m_barray.size()); - return true; -} - -void AudioWriterWAV::freeup() { m_barray.clear(); } - //----------------------------------------------------------------------------- // AudioLevelsDisplay Class //----------------------------------------------------------------------------- AudioLevelsDisplay::AudioLevelsDisplay(QWidget *parent) - : QWidget(parent), m_level(0.0) { + : QWidget(parent), m_level(0.0), m_peakL(0.0) { setFixedHeight(20); setFixedWidth(300); } -void AudioLevelsDisplay::setLevel(qreal level) { - if (m_level != level) { +void AudioLevelsDisplay::setLevel(qreal level, qreal peakLevel) { + if (m_level != level || m_peakL != peakLevel) { m_level = level; + m_peakL = peakLevel; update(); } } @@ -831,9 +888,18 @@ void AudioLevelsDisplay::paintEvent(QPaintEvent *event) { } else color = Qt::red; - qreal widthLevel = m_level * width(); - painter.fillRect(0, 0, widthLevel, height(), color); + int widthLevel = m_level * width(); + int widthPeakL = m_peakL * width(); painter.fillRect(widthLevel, 0, width(), height(), Qt::black); + if (widthPeakL >= 0) { + if (m_peakL >= 0.995) { + painter.fillRect(width() - 4, 0, 4, height(), Qt::red); + } else { + painter.setPen(QColor(64, 64, 64)); // very dark gray + painter.drawLine(widthPeakL, 0, widthPeakL, height()); + } + } + painter.fillRect(0, 0, widthLevel, height(), color); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/audiorecordingpopup.h b/toonz/sources/toonz/audiorecordingpopup.h index ec3e3f88..e0149572 100644 --- a/toonz/sources/toonz/audiorecordingpopup.h +++ b/toonz/sources/toonz/audiorecordingpopup.h @@ -97,22 +97,26 @@ public: AudioWriterWAV(const QAudioFormat &format); bool restart(const QAudioFormat &format); - void start(); - void stop(); - bool save(const QString &filename); - void freeup(); + bool start(const QString &filename, bool useMem); + bool stop(); qint64 readData(char *data, qint64 maxlen) override; qint64 writeData(const char *data, qint64 len) override; qreal level() const { return m_level; } + qreal peakLevel() const { return m_peakL; } private: - QByteArray m_barray; + QString m_filename; + QFile *m_wavFile; + QByteArray *m_wavBuff; // if not null then use memory QAudioFormat m_format; + quint64 m_wrRawB; // Written raw bytes qreal m_rbytesms; qreal m_maxAmp; - qreal m_level; + qreal m_level, m_peakL; + + void writeWAVHeader(QFile &file); signals: void update(qint64 duration); @@ -128,12 +132,13 @@ public: explicit AudioLevelsDisplay(QWidget *parent = 0); // Using [0; 1.0] range - void setLevel(qreal level); + void setLevel(qreal level, qreal peak); protected: void paintEvent(QPaintEvent *event); private: qreal m_level; + qreal m_peakL; }; #endif \ No newline at end of file From e171fd5994e8cc3ee7a4e0e4482af9128c74e78d Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 6 Dec 2021 09:44:36 +0900 Subject: [PATCH 10/18] fix shortcut loading from file --- toonz/sources/toonz/shortcutpopup.cpp | 117 ++++++++++---------------- toonz/sources/toonz/shortcutpopup.h | 4 +- 2 files changed, 45 insertions(+), 76 deletions(-) diff --git a/toonz/sources/toonz/shortcutpopup.cpp b/toonz/sources/toonz/shortcutpopup.cpp index afd6dce2..0d1ec146 100644 --- a/toonz/sources/toonz/shortcutpopup.cpp +++ b/toonz/sources/toonz/shortcutpopup.cpp @@ -470,7 +470,7 @@ ShortcutPopup::ShortcutPopup() connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(onSearchTextChanged(const QString &))); connect(m_presetChoiceCB, SIGNAL(currentIndexChanged(int)), - SLOT(onPresetChanged(int))); + SLOT(onPresetChanged())); connect(m_exportButton, SIGNAL(clicked()), SLOT(onExportButton())); connect(m_deletePresetButton, SIGNAL(clicked()), SLOT(onDeletePreset())); connect(m_savePresetButton, SIGNAL(clicked()), SLOT(onSavePreset())); @@ -495,8 +495,8 @@ void ShortcutPopup::onSearchTextChanged(const QString &text) { //----------------------------------------------------------------------------- -void ShortcutPopup::onPresetChanged(int index) { - if (m_presetChoiceCB->currentText() == "Load from file...") { +void ShortcutPopup::onPresetChanged() { + if (m_presetChoiceCB->currentData().toString() == QString("LoadFromFile")) { importPreset(); } } @@ -607,7 +607,10 @@ void ShortcutPopup::onExportButton(TFilePath fp) { if (fp == TFilePath()) return; } showDialog(tr("Saving Shortcuts")); - QString shortcutString = "[shortcuts]\n"; + + QSettings preset(toQString(fp), QSettings::IniFormat); + preset.beginGroup("shortcuts"); + for (int commandType = UndefinedCommandType; commandType <= MenuCommandType; commandType++) { std::vector actions; @@ -618,24 +621,22 @@ void ShortcutPopup::onExportButton(TFilePath fp) { std::string shortcut = CommandManager::instance()->getShortcutFromAction(action); if (shortcut != "") { - shortcutString = shortcutString + QString::fromStdString(id) + "=" + - QString::fromStdString(shortcut) + "\n"; + preset.setValue(QString::fromStdString(id), + QString::fromStdString(shortcut)); } } } - QFile file(fp.getQString()); - file.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream out(&file); - out << shortcutString; - file.close(); + + preset.endGroup(); + m_dialog->hide(); } //----------------------------------------------------------------------------- void ShortcutPopup::onDeletePreset() { - // change this to 4 once RETAS shortcuts are updated - if (m_presetChoiceCB->currentIndex() <= 3) { + // change this to 5 once RETAS shortcuts are updated + if (m_presetChoiceCB->currentIndex() <= 4) { DVGui::MsgBox(DVGui::CRITICAL, tr("Included presets cannot be deleted.")); return; } @@ -649,7 +650,7 @@ void ShortcutPopup::onDeletePreset() { } TFilePath presetDir = ToonzFolder::getMyModuleDir() + TFilePath("shortcutpresets"); - QString presetName = m_presetChoiceCB->currentText(); + QString presetName = m_presetChoiceCB->currentData().toString(); if (TSystem::doesExistFileOrLevel(presetDir + TFilePath(presetName + ".ini"))) { TSystem::deleteFile(presetDir + TFilePath(presetName + ".ini")); @@ -693,48 +694,26 @@ void ShortcutPopup::importPreset() { //----------------------------------------------------------------------------- void ShortcutPopup::onLoadPreset() { - QString preset = m_presetChoiceCB->currentText(); - TFilePath presetDir = - ToonzFolder::getMyModuleDir() + TFilePath("shortcutpresets"); - TFilePath defaultPresetDir = - ToonzFolder::getProfileFolder() + TFilePath("layouts/shortcuts"); - if (preset == "") return; - if (preset == "Load from file...") { + QString preset = m_presetChoiceCB->currentData().toString(); + TFilePath presetDir; + if (m_presetChoiceCB->currentIndex() <= 4) + presetDir = + ToonzFolder::getProfileFolder() + TFilePath("layouts/shortcuts"); + else + presetDir = ToonzFolder::getMyModuleDir() + TFilePath("shortcutpresets"); + + if (preset.isEmpty()) return; + if (preset == QString("LoadFromFile")) { importPreset(); return; } if (!showConfirmDialog()) return; showDialog(tr("Setting Shortcuts")); - if (preset == "Tahoma2D") { + TFilePath presetFilePath(preset + ".ini"); + if (TSystem::doesExistFileOrLevel(presetDir + presetFilePath)) { clearAllShortcuts(false); - TFilePath fp = defaultPresetDir + TFilePath("deftahoma2d.ini"); - setPresetShortcuts(fp); - return; - } else if (preset == "Toon Boom Harmony") { - clearAllShortcuts(false); - TFilePath fp = defaultPresetDir + TFilePath("otharmony.ini"); - setPresetShortcuts(fp); - return; - } else if (preset == "Adobe Animate") { - clearAllShortcuts(false); - TFilePath fp = defaultPresetDir + TFilePath("otanimate.ini"); - setPresetShortcuts(fp); - return; - } else if (preset == "Adobe Flash Pro") { - clearAllShortcuts(false); - TFilePath fp = defaultPresetDir + TFilePath("otadobe.ini"); - setPresetShortcuts(fp); - return; - } else if (preset == "RETAS PaintMan") { - clearAllShortcuts(false); - TFilePath fp = defaultPresetDir + TFilePath("otretas.ini"); - setPresetShortcuts(fp); - return; - } else if (TSystem::doesExistFileOrLevel(presetDir + - TFilePath(preset + ".ini"))) { - clearAllShortcuts(false); - TFilePath fp = presetDir + TFilePath(preset + ".ini"); + TFilePath fp = presetDir + presetFilePath; setPresetShortcuts(fp); return; } @@ -743,14 +722,16 @@ void ShortcutPopup::onLoadPreset() { //----------------------------------------------------------------------------- -QStringList ShortcutPopup::buildPresets() { - QStringList presets; - presets << "" - << "Tahoma2D" - //<< "RETAS PaintMan" - << "Toon Boom Harmony" - << "Adobe Animate" - << "Adobe Flash Pro"; +void ShortcutPopup::buildPresets() { + m_presetChoiceCB->clear(); + + m_presetChoiceCB->addItem("", QString("")); + m_presetChoiceCB->addItem("Tahoma2D", QString("deftahoma2d")); + // m_presetChoiceCB->addItem("RETAS PaintMan", QString("otretas")); + m_presetChoiceCB->addItem("Toon Boom Harmony", QString("otharmony")); + m_presetChoiceCB->addItem("Adobe Animate", QString("otanimate")); + m_presetChoiceCB->addItem("Adobe Flash Pro", QString("otadobe")); + TFilePath presetDir = ToonzFolder::getMyModuleDir() + TFilePath("shortcutpresets"); if (TSystem::doesExistFileOrLevel(presetDir)) { @@ -763,12 +744,10 @@ QStringList ShortcutPopup::buildPresets() { } } customPresets.sort(); - presets = presets + customPresets; + for (auto customPreset : customPresets) + m_presetChoiceCB->addItem(customPreset, customPreset); } - presets << tr("Load from file..."); - m_presetChoiceCB->clear(); - m_presetChoiceCB->addItems(presets); - return presets; + m_presetChoiceCB->addItem(tr("Load from file..."), QString("LoadFromFile")); } //----------------------------------------------------------------------------- @@ -808,19 +787,9 @@ void ShortcutPopup::setCurrentPresetPref(QString name) { void ShortcutPopup::getCurrentPresetPref() { QString name = Preferences::instance()->getShortcutPreset(); - if (name == "DELETED") - m_presetChoiceCB->setCurrentText(""); - else if (name == "deftahoma2d") - m_presetChoiceCB->setCurrentText("Tahoma2D"); - else if (name == "otharmony") - m_presetChoiceCB->setCurrentText("Toon Boom Harmony"); - else if (name == "otadobe") - m_presetChoiceCB->setCurrentText("Adobe Animate(Flash)"); - else if (name == "otretas") - m_presetChoiceCB->setCurrentText("RETAS PaintMan"); + if (name == "DELETED") name = ""; - else - m_presetChoiceCB->setCurrentText(name); + m_presetChoiceCB->setCurrentIndex(m_presetChoiceCB->findData(name)); } OpenPopupCommandHandler openShortcutPopup(MI_ShortcutPopup); diff --git a/toonz/sources/toonz/shortcutpopup.h b/toonz/sources/toonz/shortcutpopup.h index d544c1ac..4a05c07a 100644 --- a/toonz/sources/toonz/shortcutpopup.h +++ b/toonz/sources/toonz/shortcutpopup.h @@ -115,7 +115,7 @@ private: bool showConfirmDialog(); bool showOverwriteDialog(QString name); void importPreset(); - QStringList buildPresets(); + void buildPresets(); void showEvent(QShowEvent *se) override; void setCurrentPresetPref(QString preset); void getCurrentPresetPref(); @@ -123,7 +123,7 @@ private: protected slots: void clearAllShortcuts(bool warning = true); void onSearchTextChanged(const QString &text); - void onPresetChanged(int index); + void onPresetChanged(); void onExportButton(TFilePath fp = TFilePath()); void onDeletePreset(); void onSavePreset(); From 5083a56d275346da8500f10ffed0aae6cc22cc22 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 22 Nov 2021 18:49:42 +0900 Subject: [PATCH 11/18] cell mark --- toonz/sources/include/orientation.h | 1 + toonz/sources/include/toonz/sceneproperties.h | 14 + toonz/sources/include/toonz/txshcolumn.h | 11 + .../sources/include/toonzqt/menubarcommand.h | 3 +- toonz/sources/toonz/exportxsheetpdf.cpp | 230 +++++++++- toonz/sources/toonz/exportxsheetpdf.h | 20 +- toonz/sources/toonz/filebrowserpopup.cpp | 5 +- toonz/sources/toonz/filebrowserpopup.h | 2 +- toonz/sources/toonz/mainwindow.cpp | 10 + toonz/sources/toonz/menubarcommandids.h | 3 + toonz/sources/toonz/scenesettingspopup.cpp | 182 +++++++- toonz/sources/toonz/scenesettingspopup.h | 23 + toonz/sources/toonz/shortcutpopup.cpp | 2 + toonz/sources/toonz/tpanels.cpp | 15 +- toonz/sources/toonz/tpanels.h | 2 +- toonz/sources/toonz/xdtsimportpopup.cpp | 62 ++- toonz/sources/toonz/xdtsimportpopup.h | 10 +- toonz/sources/toonz/xdtsio.cpp | 174 ++++++- toonz/sources/toonz/xdtsio.h | 11 +- toonz/sources/toonz/xshcellviewer.cpp | 427 +++++++++++++++--- toonz/sources/toonz/xshcellviewer.h | 19 +- toonz/sources/toonz/xsheetcmd.cpp | 38 ++ toonz/sources/toonzlib/orientation.cpp | 6 + toonz/sources/toonzlib/sceneproperties.cpp | 76 +++- toonz/sources/toonzlib/txshcolumn.cpp | 78 +++- toonz/sources/toonzlib/txsheet.cpp | 14 + toonz/sources/toonzlib/txshlevelcolumn.cpp | 8 +- toonz/sources/toonzlib/txshmeshcolumn.cpp | 4 + toonz/sources/toonzlib/txshpalettecolumn.cpp | 5 + toonz/sources/toonzlib/txshsoundcolumn.cpp | 9 + .../sources/toonzlib/txshsoundtextcolumn.cpp | 4 + toonz/sources/toonzlib/txshzeraryfxcolumn.cpp | 4 + toonz/sources/toonzqt/menubarcommand.cpp | 15 +- toonz/sources/toonzqt/stageschematicnode.cpp | 9 +- 34 files changed, 1338 insertions(+), 158 deletions(-) diff --git a/toonz/sources/include/orientation.h b/toonz/sources/include/orientation.h index 9ec14741..0199cfd5 100644 --- a/toonz/sources/include/orientation.h +++ b/toonz/sources/include/orientation.h @@ -67,6 +67,7 @@ enum class PredefinedRect { BEGIN_EXTENDER, //! top / left extender KEYFRAME_AREA, //! part of cell dedicated to key frames DRAG_AREA, //! draggable side bar + CELL_MARK_AREA, //! cell mark SOUND_TRACK, //! area dedicated to waveform display PREVIEW_TRACK, //! sound preview area BEGIN_SOUND_EDIT, //! top sound resize diff --git a/toonz/sources/include/toonz/sceneproperties.h b/toonz/sources/include/toonz/sceneproperties.h index 6d7f2310..2ac27e1f 100644 --- a/toonz/sources/include/toonz/sceneproperties.h +++ b/toonz/sources/include/toonz/sceneproperties.h @@ -49,6 +49,11 @@ class DVAPI TSceneProperties { public: typedef std::vector Guides; + struct CellMark { + QString name; + TPixel32 color; + }; + private: Guides m_hGuides, m_vGuides; @@ -73,6 +78,9 @@ private: //! Xsheet Note Color, color number = 7. QList m_notesColor; + // Cell Mark colors and names + QList m_cellMarks; + bool m_columnColorFilterOnRender; TFilePath m_camCapSaveInPath; @@ -274,6 +282,12 @@ and height. TPixel32 getNoteColor(int colorIndex) const; void setNoteColor(TPixel32 color, int colorIndex); + QList getCellMarks() const; + CellMark getCellMark(int index) const; + void setCellMark(const CellMark &mark, int index); + bool hasDefaultCellMarks() + const; // check if the cell mark settings are modified + private: // not implemented TSceneProperties(const TSceneProperties &); diff --git a/toonz/sources/include/toonz/txshcolumn.h b/toonz/sources/include/toonz/txshcolumn.h index 526e08b7..480ed333 100644 --- a/toonz/sources/include/toonz/txshcolumn.h +++ b/toonz/sources/include/toonz/txshcolumn.h @@ -9,6 +9,7 @@ #include #include +#include #undef DVAPI #undef DVVAR @@ -304,6 +305,9 @@ protected: std::vector m_cells; int m_first; + // cell marks information key:frame value:id + QMap m_cellMarkIds; + public: /*! Constructs a TXshCellColumn with default value. @@ -398,6 +402,13 @@ last row with not empty cell of same level. bool getLevelRange(int row, int &r0, int &r1) const override; // virtual void updateIcon() = 0; + + void saveCellMarks(TOStream &os); + bool loadCellMarks(std::string tagName, TIStream &is); + void setCellMark(int frame, int id); + int getCellMark(int frame) const; + QMap getCellMarks() const; + void clearCellMarks(); }; #endif diff --git a/toonz/sources/include/toonzqt/menubarcommand.h b/toonz/sources/include/toonzqt/menubarcommand.h index 20930951..fa30ea1d 100644 --- a/toonz/sources/include/toonzqt/menubarcommand.h +++ b/toonz/sources/include/toonzqt/menubarcommand.h @@ -65,7 +65,8 @@ enum CommandType { MiscCommandType, MenuCommandType, VisualizationButtonCommandType, - StopMotionCommandType + StopMotionCommandType, + CellMarkCommandType }; //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/exportxsheetpdf.cpp b/toonz/sources/toonz/exportxsheetpdf.cpp index 2589a272..d1425af8 100644 --- a/toonz/sources/toonz/exportxsheetpdf.cpp +++ b/toonz/sources/toonz/exportxsheetpdf.cpp @@ -90,6 +90,12 @@ TEnv::StringVar XShPdfExportImgPath("XShPdfExportImgPath", ""); TEnv::IntVar XShPdfExportContinuousLineThres("XShPdfExportContinuousLineThres", 0); +TEnv::IntVar XShPdfExportTick1Id("XShPdfExportTick1Id", -1); +TEnv::IntVar XShPdfExportTick2Id("XShPdfExportTick2Id", -1); +TEnv::IntVar XShPdfExportKeyId("XShPdfExportKeyId", -1); +TEnv::IntVar XShPdfExportTick1Type("XShPdfExportTick1Type", TickMark_Dot); +TEnv::IntVar XShPdfExportTick2Type("XShPdfExportTick2Type", TickMark_Dot); + using namespace XSheetPDFTemplateParamIDs; namespace { @@ -287,6 +293,86 @@ XSheetPDFDataType dataStr2Type(const QString& str) { return map.value(str, Data_Invalid); } +QIcon getColorChipIcon(TPixel32 color) { + QPixmap pm(15, 15); + pm.fill(QColor(color.r, color.g, color.b)); + return QIcon(pm); +} + +void refreshCellMarkComboItems(QComboBox* combo) { + int current = -1; + if (combo->count()) current = combo->currentData().toInt(); + + combo->clear(); + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + combo->addItem(QObject::tr("None", "XSheetPDF CellMark"), -1); + int curId = 0; + for (auto mark : marks) { + QString label = QString("%1: %2").arg(curId).arg(mark.name); + combo->addItem(getColorChipIcon(mark.color), label, curId); + curId++; + } + + if (current >= 0) combo->setCurrentIndex(combo->findData(current)); +} + +QPixmap tickMarkPm(TickMarkType type, int size, bool withBG = false) { + QPixmap pm(size, size); + QPointF center(double(size) * 0.5, double(size) * 0.5); + + pm.fill((withBG) ? Qt::white : Qt::transparent); + QPainter p(&pm); + QPen pen(Qt::black); + pen.setWidthF(double(size) / 15.0); + p.setPen(pen); + switch (type) { + case TickMark_Dot: { + p.setBrush(Qt::black); + double dotR = double(size) * 0.1; + p.drawEllipse(center, dotR, dotR); + break; + } + case TickMark_Circle: { + double circleR = double(size) * 0.4; + p.drawEllipse(center, circleR, circleR); + break; + } + case TickMark_Filled: { + p.setBrush(Qt::black); + double circleR = double(size) * 0.4; + p.drawEllipse(center, circleR, circleR); + break; + } + case TickMark_Asterisk: { + QFont font = p.font(); + font.setPixelSize(size); + p.setFont(font); + p.drawText(0, 0, size, size, Qt::AlignCenter, "*"); + break; + } + } + return pm; +} + +QComboBox* createTickMarkCombo(QWidget* parent) { + QComboBox* combo = new QComboBox(parent); + combo->addItem(QIcon(tickMarkPm(TickMark_Dot, 15, true)), + QObject::tr("Dot", "XSheetPDF CellMark"), TickMark_Dot); + combo->addItem(QIcon(tickMarkPm(TickMark_Circle, 15, true)), + QObject::tr("Circle", "XSheetPDF CellMark"), TickMark_Circle); + combo->addItem(QIcon(tickMarkPm(TickMark_Filled, 15, true)), + QObject::tr("Filled circle", "XSheetPDF CellMark"), + TickMark_Filled); + combo->addItem(QIcon(tickMarkPm(TickMark_Asterisk, 15, true)), + QObject::tr("Asterisk", "XSheetPDF CellMark"), + TickMark_Asterisk); + return combo; +} + } // namespace //--------------------------------------------------------- @@ -859,7 +945,7 @@ void XSheetPDFTemplate::drawContinuousLine(QPainter& painter, QRect rect, } void XSheetPDFTemplate::drawCellNumber(QPainter& painter, QRect rect, - TXshCell& cell) { + TXshCell& cell, bool isKey) { QFont font = painter.font(); font.setPixelSize(param(RowHeight) - mm2px(1)); font.setLetterSpacing(QFont::PercentageSpacing, 100); @@ -878,9 +964,34 @@ void XSheetPDFTemplate::drawCellNumber(QPainter& painter, QRect rect, str += cell.m_frameId.getLetter(); } painter.drawText(rect, Qt::AlignCenter, str); + if (isKey) { + QPen keep(painter.pen()); + QPen circlePen(keep); + circlePen.setWidth(mm2px(0.3)); + painter.setPen(circlePen); + QFontMetrics fm(font); +#if QT_VERSION >= 0x051100 + int keyR_width = + std::max(param(RowHeight), fm.horizontalAdvance(str) + mm2px(1)); +#else + int keyR_width = + std::max(param(RowHeight), fm.boundingRect(str).width() + mm2px(1)); +#endif + QRect keyR(0, 0, keyR_width, param(RowHeight)); + keyR.moveCenter(rect.center()); + painter.drawEllipse(keyR); + painter.setPen(keep); + } } } +void XSheetPDFTemplate::drawTickMark(QPainter& painter, QRect rect, + TickMarkType type) { + QRect tickR(0, 0, rect.height(), rect.height()); + tickR.moveCenter(rect.center()); + painter.drawPixmap(tickR, tickMarkPm(type, rect.height())); +} + void XSheetPDFTemplate::drawEndMark(QPainter& painter, QRect upperRect) { QRect rect = upperRect.translated(0, upperRect.height()); @@ -1136,14 +1247,23 @@ void XSheetPDFTemplate::drawXsheetContents(QPainter& painter, int framePage, TXshCell cell = column->getCell(f); if (cell.m_level != level) cell.m_level = nullptr; + int markId = column->getCellMark(r); + // cotinuous line if (r != 0 && r != 72 && prevCell == cell) { - if (drawCLFlag) + // draw tick mark + if (markId >= 0 && m_info.tick1MarkId == markId) + drawTickMark(painter, m_cellRects[c][r], m_info.tick1MarkType); + else if (markId >= 0 && m_info.tick2MarkId == markId) + drawTickMark(painter, m_cellRects[c][r], m_info.tick2MarkType); + + else if (drawCLFlag) drawContinuousLine(painter, m_cellRects[c][r], cell.isEmpty()); } // draw cell else { - drawCellNumber(painter, m_cellRects[c][r], cell); + bool drawKeyMark = (markId >= 0 && m_info.keyMarkId == markId); + drawCellNumber(painter, m_cellRects[c][r], cell, drawKeyMark); drawCLFlag = (m_info.continuousLineMode == Line_Always) ? true : (m_info.continuousLineMode == Line_None) @@ -1621,6 +1741,15 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() QPushButton* exportPngBtn = new QPushButton(tr("Export PNG"), this); QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this); + m_tick1IdCombo = new QComboBox(this); + m_tick2IdCombo = new QComboBox(this); + m_keyIdCombo = new QComboBox(this); + refreshCellMarkComboItems(m_tick1IdCombo); + refreshCellMarkComboItems(m_tick2IdCombo); + refreshCellMarkComboItems(m_keyIdCombo); + m_tick1MarkCombo = createTickMarkCombo(this); + m_tick2MarkCombo = createTickMarkCombo(this); + //------ QStringList pdfFileTypes = {"pdf"}; m_pathFld->setFilters(pdfFileTypes); @@ -1748,22 +1877,41 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() exportLay->addWidget(m_continuousLineCombo, 2, 1, 1, 2, Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_addDateTimeCB, 3, 0, 1, 3, - Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_addScenePathCB, 4, 0, 1, 3, - Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_drawSoundCB, 5, 0, 1, 3, - Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_addSceneNameCB, 6, 0, 1, 2, - Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_sceneNameEdit, 6, 2, - Qt::AlignLeft | Qt::AlignVCenter); - exportLay->addWidget(m_levelNameOnBottomCB, 7, 0, 1, 3, - Qt::AlignLeft | Qt::AlignVCenter); + QGridLayout* checksLay = new QGridLayout(); + checksLay->setMargin(0); + checksLay->setHorizontalSpacing(10); + checksLay->setVerticalSpacing(10); + { + checksLay->addWidget(m_addDateTimeCB, 0, 0); + checksLay->addWidget(m_addScenePathCB, 0, 1, 1, 2); + checksLay->addWidget(m_drawSoundCB, 1, 0); + checksLay->addWidget(m_addSceneNameCB, 1, 1); + checksLay->addWidget(m_sceneNameEdit, 1, 2, + Qt::AlignLeft | Qt::AlignVCenter); + checksLay->addWidget(m_levelNameOnBottomCB, 2, 0, 1, 3); + } + checksLay->setColumnStretch(0, 2); + checksLay->setColumnStretch(1, 1); + checksLay->setColumnStretch(2, 1); + exportLay->addLayout(checksLay, 3, 0, 1, 3); - exportLay->addWidget(new QLabel(tr("Memo:"), this), 8, 0, + exportLay->addWidget(new QLabel(tr("Inbetween mark:"), this), 4, 0, + Qt::AlignRight | Qt::AlignVCenter); + exportLay->addWidget(m_tick1IdCombo, 4, 1); + exportLay->addWidget(m_tick1MarkCombo, 4, 2, + Qt::AlignLeft | Qt::AlignVCenter); + exportLay->addWidget(new QLabel(tr("Reverse sheet mark:"), this), 5, + 0, Qt::AlignRight | Qt::AlignVCenter); + exportLay->addWidget(m_tick2IdCombo, 5, 1); + exportLay->addWidget(m_tick2MarkCombo, 5, 2, + Qt::AlignLeft | Qt::AlignVCenter); + exportLay->addWidget(new QLabel(tr("Keyframe mark:"), this), 6, 0, + Qt::AlignRight | Qt::AlignVCenter); + exportLay->addWidget(m_keyIdCombo, 6, 1); + + exportLay->addWidget(new QLabel(tr("Memo:"), this), 7, 0, Qt::AlignRight | Qt::AlignTop); - exportLay->addWidget(m_memoEdit, 8, 1, 1, 2); + exportLay->addWidget(m_memoEdit, 7, 1, 1, 2); } exportLay->setColumnStretch(2, 1); exportGBox->setLayout(exportLay); @@ -1850,6 +1998,16 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() connect(m_prev, SIGNAL(clicked(bool)), this, SLOT(onPrev())); connect(m_next, SIGNAL(clicked(bool)), this, SLOT(onNext())); + connect(m_tick1IdCombo, SIGNAL(activated(int)), this, + SLOT(onTickIdComboActivated())); + connect(m_tick2IdCombo, SIGNAL(activated(int)), this, + SLOT(onTickIdComboActivated())); + connect(m_keyIdCombo, SIGNAL(activated(int)), this, SLOT(updatePreview())); + connect(m_tick1MarkCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); + connect(m_tick2MarkCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); + // The following lines are "translation word book" listing the words which may // appear in the template @@ -1969,6 +2127,10 @@ void ExportXsheetPdfPopup::initialize() { initTemplate(); m_previewPane->fitScaleTo(m_previewArea->size()); + + refreshCellMarkComboItems(m_tick1IdCombo); + refreshCellMarkComboItems(m_tick2IdCombo); + refreshCellMarkComboItems(m_keyIdCombo); } // register settings to the user env file on close @@ -1996,6 +2158,12 @@ void ExportXsheetPdfPopup::saveSettings() { (ContinuousLineMode)(m_continuousLineCombo->currentData().toInt()); XShPdfExportContinuousLineThres = (clMode == Line_Always) ? 0 : (clMode == Line_None) ? -1 : 3; + + XShPdfExportTick1Id = m_tick1IdCombo->currentData().toInt(); + XShPdfExportTick2Id = m_tick2IdCombo->currentData().toInt(); + XShPdfExportKeyId = m_keyIdCombo->currentData().toInt(); + XShPdfExportTick1Type = m_tick1MarkCombo->currentData().toInt(); + XShPdfExportTick2Type = m_tick2MarkCombo->currentData().toInt(); } // load settings from the user env file on ctor @@ -2036,6 +2204,19 @@ void ExportXsheetPdfPopup::loadSettings() { m_logoTextEdit->setEnabled(m_logoTxtRB->isChecked()); m_logoImgPathField->setEnabled(m_logoImgRB->isChecked()); m_sceneNameEdit->setEnabled(m_addSceneNameCB->isChecked()); + + int id = XShPdfExportTick1Id; + m_tick1IdCombo->setCurrentIndex(m_tick1IdCombo->findData(id)); + m_tick1MarkCombo->setEnabled(id != -1); + id = XShPdfExportTick2Id; + m_tick2IdCombo->setCurrentIndex(m_tick2IdCombo->findData(id)); + m_tick2MarkCombo->setEnabled(id != -1); + id = XShPdfExportKeyId; + m_keyIdCombo->setCurrentIndex(m_keyIdCombo->findData(id)); + int type = XShPdfExportTick1Type; + m_tick1MarkCombo->setCurrentIndex(m_tick1MarkCombo->findData(type)); + type = XShPdfExportTick2Type; + m_tick2MarkCombo->setCurrentIndex(m_tick2MarkCombo->findData(type)); } void ExportXsheetPdfPopup::initTemplate() { @@ -2096,6 +2277,12 @@ void ExportXsheetPdfPopup::setInfo() { info.serialFrameNumber = m_serialFrameNumberCB->isChecked(); info.drawLevelNameOnBottom = m_levelNameOnBottomCB->isChecked(); + info.tick1MarkId = m_tick1IdCombo->currentData().toInt(); + info.tick2MarkId = m_tick2IdCombo->currentData().toInt(); + info.keyMarkId = m_keyIdCombo->currentData().toInt(); + info.tick1MarkType = (TickMarkType)(m_tick1MarkCombo->currentData().toInt()); + info.tick2MarkType = (TickMarkType)(m_tick2MarkCombo->currentData().toInt()); + m_currentTmpl->setInfo(info); if (!m_logoImgRB->isChecked()) return; @@ -2365,6 +2552,15 @@ void ExportXsheetPdfPopup::onNext() { updatePreview(); } +void ExportXsheetPdfPopup::onTickIdComboActivated() { + QComboBox* combo = qobject_cast(sender()); + if (combo == m_tick1IdCombo) + m_tick1MarkCombo->setEnabled(m_tick1IdCombo->currentData().toInt() != -1); + else if (combo == m_tick2IdCombo) + m_tick2MarkCombo->setEnabled(m_tick2IdCombo->currentData().toInt() != -1); + updatePreview(); +} + //----------------------------------------------------------------------------- OpenPopupCommandHandler openExportXsheetPdfPopup( diff --git a/toonz/sources/toonz/exportxsheetpdf.h b/toonz/sources/toonz/exportxsheetpdf.h index b9c73b14..e55e595b 100644 --- a/toonz/sources/toonz/exportxsheetpdf.h +++ b/toonz/sources/toonz/exportxsheetpdf.h @@ -85,6 +85,12 @@ typedef void (*DecoFunc)(QPainter&, QRect, QMap&, enum ExportArea { Area_Actions = 0, Area_Cells }; enum ContinuousLineMode { Line_Always = 0, Line_MoreThan3s, Line_None }; +enum TickMarkType { + TickMark_Dot = 0, + TickMark_Circle, + TickMark_Filled, + TickMark_Asterisk +}; struct XSheetPDFFormatInfo { QColor lineColor; @@ -101,6 +107,11 @@ struct XSheetPDFFormatInfo { bool serialFrameNumber; bool drawLevelNameOnBottom; ContinuousLineMode continuousLineMode; + int tick1MarkId; + int tick2MarkId; + int keyMarkId; + TickMarkType tick1MarkType; + TickMarkType tick2MarkType; }; class XSheetPDFTemplate { @@ -172,7 +183,9 @@ protected: void addInfo(int w, QString lbl, DecoFunc f = nullptr); void drawContinuousLine(QPainter& painter, QRect rect, bool isEmpty); - void drawCellNumber(QPainter& painter, QRect rect, TXshCell& cell); + void drawCellNumber(QPainter& painter, QRect rect, TXshCell& cell, + bool isKey); + void drawTickMark(QPainter& painter, QRect rect, TickMarkType type); void drawEndMark(QPainter& painter, QRect upperRect); void drawLevelName(QPainter& painter, QRect rect, QString name, bool isBottom = false); @@ -281,6 +294,9 @@ class ExportXsheetPdfPopup final : public DVGui::Dialog { int m_totalPageCount; QPushButton *m_prev, *m_next; + QComboBox *m_tick1IdCombo, *m_tick2IdCombo, *m_keyIdCombo; + QComboBox *m_tick1MarkCombo, *m_tick2MarkCombo; + // column and column name (if manually specified) QList> m_columns; QList m_soundColumns; @@ -315,6 +331,8 @@ protected slots: void onLogoImgPathChanged(); void onPrev(); void onNext(); + + void onTickIdComboActivated(); }; #endif \ No newline at end of file diff --git a/toonz/sources/toonz/filebrowserpopup.cpp b/toonz/sources/toonz/filebrowserpopup.cpp index e2701bef..8975a66d 100644 --- a/toonz/sources/toonz/filebrowserpopup.cpp +++ b/toonz/sources/toonz/filebrowserpopup.cpp @@ -479,8 +479,9 @@ TFilePath GenericLoadFilePopup::getPath() { // GenericSaveFilePopup implementation //*********************************************************************************** -GenericSaveFilePopup::GenericSaveFilePopup(const QString &title) - : FileBrowserPopup(title, Options(FOR_SAVING)) { +GenericSaveFilePopup::GenericSaveFilePopup(const QString &title, + QWidget *customWidget) + : FileBrowserPopup(title, Options(FOR_SAVING), "", customWidget) { connect(m_nameField, SIGNAL(returnPressedNow()), m_okButton, SLOT(animateClick())); } diff --git a/toonz/sources/toonz/filebrowserpopup.h b/toonz/sources/toonz/filebrowserpopup.h index a4e0f1ae..a26cc8e1 100644 --- a/toonz/sources/toonz/filebrowserpopup.h +++ b/toonz/sources/toonz/filebrowserpopup.h @@ -185,7 +185,7 @@ protected: //! asks the user for a \a single file path to save something to. class GenericSaveFilePopup : public FileBrowserPopup { public: - GenericSaveFilePopup(const QString &title); + GenericSaveFilePopup(const QString &title, QWidget *customWidget = nullptr); /*! This function shows the popup and blocks until a suitable diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 501f55aa..41dffd9c 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -3066,6 +3066,16 @@ void MainWindow::defineActions() { ""); createStopMotionAction(MI_StopMotionToggleUseLiveViewImages, QT_TR_NOOP("Show original live view images."), ""); + + // create cell mark actions + for (int markId = 0; markId < 12; markId++) { + std::string cmdId = (std::string)MI_SetCellMark + std::to_string(markId); + std::string labelStr = + QT_TR_NOOP("Set Cell Mark ") + std::to_string(markId); + QAction *action = + createAction(cmdId.c_str(), labelStr.c_str(), "", "", CellMarkCommandType); + action->setData(markId); + } } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/menubarcommandids.h b/toonz/sources/toonz/menubarcommandids.h index 36ba8216..7314196a 100644 --- a/toonz/sources/toonz/menubarcommandids.h +++ b/toonz/sources/toonz/menubarcommandids.h @@ -471,6 +471,9 @@ #define MI_ExportTvpJson "MI_ExportTvpJson" #define MI_ExportXsheetPDF "MI_ExportXsheetPDF" +// mark id is added for each actual command (i.g. MI_SetCellMark1) +#define MI_SetCellMark "MI_SetCellMark" + #define MI_ToggleAutoCreate "MI_ToggleAutoCreate" #define MI_ToggleCreationInHoldCells "MI_ToggleCreationInHoldCells" #define MI_ToggleAutoStretch "MI_ToggleAutoStretch" diff --git a/toonz/sources/toonz/scenesettingspopup.cpp b/toonz/sources/toonz/scenesettingspopup.cpp index 09983456..28563468 100644 --- a/toonz/sources/toonz/scenesettingspopup.cpp +++ b/toonz/sources/toonz/scenesettingspopup.cpp @@ -32,6 +32,7 @@ #include #include #include +#include using namespace DVGui; @@ -43,6 +44,46 @@ const int labelSize = 110; //----------------------------------------------------------------------------- +class EditCellMarkUndo final : public TUndo { + int m_id; + TSceneProperties::CellMark m_markBefore, m_markAfter; + + EditCellMarkUndo(int id) : m_id(id) { + m_markBefore = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(id); + } + +public: + EditCellMarkUndo(int id, TPixel32 color) : EditCellMarkUndo(id) { + m_markAfter = {m_markBefore.name, color}; + } + EditCellMarkUndo(int id, QString name) : EditCellMarkUndo(id) { + m_markAfter = {name, m_markBefore.color}; + } + + void set(const TSceneProperties::CellMark &mark) const { + TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->setCellMark(mark, m_id); + TApp::instance()->getCurrentScene()->notifySceneChanged(); + } + + void undo() const override { set(m_markBefore); } + + void redo() const override { set(m_markAfter); } + + int getSize() const override { return sizeof *this; } + + QString getHistoryString() override { + return QObject::tr("Edit Cell Mark #%1").arg(QString::number(m_id)); + } +}; + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- } // namespace @@ -77,12 +118,129 @@ QImage::Format_ARGB32); }; */ +//============================================================================= +// CellMarksPopup +//----------------------------------------------------------------------------- + +CellMarksPopup::CellMarksPopup(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Cell Marks Settings")); + + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + + QGridLayout *layout = new QGridLayout(); + layout->setMargin(10); + layout->setHorizontalSpacing(5); + layout->setVerticalSpacing(10); + { + int id = 0; + for (auto mark : marks) { + ColorField *colorF = new ColorField(this, false, mark.color, 20); + colorF->hideChannelsFields(true); + QLineEdit *nameF = new QLineEdit(mark.name, this); + m_fields.append({id, colorF, nameF}); + + int row = layout->rowCount(); + + layout->addWidget(new QLabel(QString("%1:").arg(id), this), row, 0, + Qt::AlignRight | Qt::AlignVCenter); + + layout->addWidget(colorF, row, 1); + layout->addWidget(nameF, row, 2); + + connect(colorF, SIGNAL(colorChanged(const TPixel32 &, bool)), this, + SLOT(onColorChanged(const TPixel32 &, bool))); + connect(nameF, SIGNAL(editingFinished()), this, SLOT(onNameChanged())); + id++; + } + } + layout->setColumnStretch(2, 1); + setLayout(layout); +} + +void CellMarksPopup::update() { + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + assert(marks.count() == m_fields.count()); + int id = 0; + for (auto mark : marks) { + assert(m_fields[id].id == id); + m_fields[id].colorField->setColor(mark.color); + m_fields[id].nameField->setText(mark.name); + id++; + } +} + +void CellMarksPopup::onColorChanged(const TPixel32 &color, bool isDragging) { + if (isDragging) return; + // obtain id + int id = -1; + ColorField *colorF = qobject_cast(sender()); + for (auto field : m_fields) { + if (field.colorField == colorF) { + id = field.id; + break; + } + } + if (id < 0) return; + + // return if the value is unchanged + TPixel32 oldColor = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(id) + .color; + if (color == oldColor) return; + + EditCellMarkUndo *undo = new EditCellMarkUndo(id, color); + undo->redo(); + TUndoManager::manager()->add(undo); +} + +void CellMarksPopup::onNameChanged() { + // obtain id + int id = -1; + QLineEdit *nameF = qobject_cast(sender()); + for (auto field : m_fields) { + if (field.nameField == nameF) { + id = field.id; + break; + } + } + if (id < 0) return; + + // return if the value is unchanged + QString oldName = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(id) + .name; + if (nameF->text() == oldName) return; + // reject empty string + if (nameF->text().isEmpty()) { + nameF->setText(oldName); + return; + } + + EditCellMarkUndo *undo = new EditCellMarkUndo(id, nameF->text()); + undo->redo(); + TUndoManager::manager()->add(undo); +} + //============================================================================= // SceneSettingsPopup //----------------------------------------------------------------------------- SceneSettingsPopup::SceneSettingsPopup() - : QDialog(TApp::instance()->getMainWindow()) { + : QDialog(TApp::instance()->getMainWindow()), m_cellMarksPopup(nullptr) { setWindowTitle(tr("Scene Settings")); setObjectName("SceneSettings"); TSceneProperties *sprop = getProperties(); @@ -124,6 +282,9 @@ SceneSettingsPopup::SceneSettingsPopup() m_colorFilterOnRenderCB->setChecked( sprop->isColumnColorFilterOnRenderEnabled()); + QPushButton *editCellMarksButton = + new QPushButton(tr("Edit Cell Marks"), this); + // layout QGridLayout *mainLayout = new QGridLayout(); mainLayout->setMargin(10); @@ -164,6 +325,12 @@ SceneSettingsPopup::SceneSettingsPopup() // Use Color Filter and Transparency for Rendering mainLayout->addWidget(m_colorFilterOnRenderCB, 6, 0, 1, 4); + + // cell marks + mainLayout->addWidget(new QLabel(tr("Cell Marks:"), this), 7, 0, + Qt::AlignRight | Qt::AlignVCenter); + mainLayout->addWidget(editCellMarksButton, 7, 1, 1, 4, + Qt::AlignLeft | Qt::AlignVCenter); } mainLayout->setColumnStretch(0, 0); mainLayout->setColumnStretch(1, 0); @@ -201,6 +368,9 @@ SceneSettingsPopup::SceneSettingsPopup() // Use Color Filter and Transparency for Rendering ret = ret && connect(m_colorFilterOnRenderCB, SIGNAL(stateChanged(int)), this, SLOT(onColorFilterOnRenderChanged())); + // Cell Marks + ret = ret && connect(editCellMarksButton, SIGNAL(clicked()), this, + SLOT(onEditCellMarksButtonClicked())); assert(ret); } @@ -254,6 +424,8 @@ void SceneSettingsPopup::update() { m_startFrameFld->setValue(markerOffset + 1); m_colorFilterOnRenderCB->setChecked( sprop->isColumnColorFilterOnRenderEnabled()); + + if (m_cellMarksPopup) m_cellMarksPopup->update(); } //----------------------------------------------------------------------------- @@ -368,6 +540,14 @@ void SceneSettingsPopup::onColorFilterOnRenderChanged() { TApp::instance()->getCurrentScene()->notifySceneChanged(); } +//----------------------------------------------------------------------------- + +void SceneSettingsPopup::onEditCellMarksButtonClicked() { + if (!m_cellMarksPopup) m_cellMarksPopup = new CellMarksPopup(this); + m_cellMarksPopup->show(); + m_cellMarksPopup->raise(); +} + //============================================================================= OpenPopupCommandHandler openSceneSettingsPopup( diff --git a/toonz/sources/toonz/scenesettingspopup.h b/toonz/sources/toonz/scenesettingspopup.h index ecd487ae..b6096a8e 100644 --- a/toonz/sources/toonz/scenesettingspopup.h +++ b/toonz/sources/toonz/scenesettingspopup.h @@ -13,6 +13,25 @@ // forward declaration class TSceneProperties; class QComboBox; +class QLineEdit; + +class CellMarksPopup final : public QDialog { + Q_OBJECT + struct MarkerField { + int id; + DVGui::ColorField *colorField; + QLineEdit *nameField; + }; + + QList m_fields; + +public: + CellMarksPopup(QWidget *parent); + void update(); +protected slots: + void onColorChanged(const TPixel32 &, bool); + void onNameChanged(); +}; //============================================================================= // SceneSettingsPopup @@ -37,6 +56,8 @@ class SceneSettingsPopup final : public QDialog { TSceneProperties *getProperties() const; + CellMarksPopup *m_cellMarksPopup; + public: SceneSettingsPopup(); void configureNotify(); @@ -61,6 +82,8 @@ public slots: void setBgColor(const TPixel32 &value, bool isDragging); void onColorFilterOnRenderChanged(); + + void onEditCellMarksButtonClicked(); }; #endif // SCENESETTINGSPOPUP_H diff --git a/toonz/sources/toonz/shortcutpopup.cpp b/toonz/sources/toonz/shortcutpopup.cpp index 0d1ec146..8e0d4994 100644 --- a/toonz/sources/toonz/shortcutpopup.cpp +++ b/toonz/sources/toonz/shortcutpopup.cpp @@ -223,6 +223,8 @@ ShortcutTree::ShortcutTree(QWidget *parent) : QTreeWidget(parent) { addFolder(tr("Help"), MenuHelpCommandType, menuCommandFolder); addFolder(tr("Right-click Menu Commands"), RightClickMenuCommandType); + QTreeWidgetItem *rcmSubFolder = m_folders.back(); + addFolder(tr("Cell Mark"), CellMarkCommandType, rcmSubFolder); addFolder(tr("Tools"), ToolCommandType); addFolder(tr("Tool Modifiers"), ToolModifierCommandType); diff --git a/toonz/sources/toonz/tpanels.cpp b/toonz/sources/toonz/tpanels.cpp index aab8cafb..c30b845e 100644 --- a/toonz/sources/toonz/tpanels.cpp +++ b/toonz/sources/toonz/tpanels.cpp @@ -796,30 +796,31 @@ void ColorFieldEditorController::edit(DVGui::ColorField *colorField) { connect(m_currentColorField, SIGNAL(colorChanged(const TPixel32 &, bool)), SLOT(onColorChanged(const TPixel32 &, bool))); connect(m_colorFieldHandle, SIGNAL(colorStyleChanged(bool)), - SLOT(onColorStyleChanged())); + SLOT(onColorStyleChanged(bool))); } //----------------------------------------------------------------------------- void ColorFieldEditorController::hide() { disconnect(m_colorFieldHandle, SIGNAL(colorStyleChanged(bool)), this, - SLOT(onColorStyleChanged())); + SLOT(onColorStyleChanged(bool))); } //----------------------------------------------------------------------------- -void ColorFieldEditorController::onColorStyleChanged() { +void ColorFieldEditorController::onColorStyleChanged(bool isDragging) { if (!m_currentColorField) return; assert(!!m_palette); TPixel32 color = m_palette->getStyle(1)->getMainColor(); - if (m_currentColorField->getColor() == color) return; + if (m_currentColorField->getColor() == color && isDragging) return; m_currentColorField->setColor(color); - m_currentColorField->notifyColorChanged(color, false); + m_currentColorField->notifyColorChanged(color, isDragging); } //----------------------------------------------------------------------------- -void ColorFieldEditorController::onColorChanged(const TPixel32 &color, bool) { +void ColorFieldEditorController::onColorChanged(const TPixel32 &color, + bool isDragging) { if (!m_currentColorField) return; TColorStyle *style = m_palette->getStyle(1); if (style->getMainColor() == color) return; @@ -827,7 +828,7 @@ void ColorFieldEditorController::onColorChanged(const TPixel32 &color, bool) { TApp::instance() ->getPaletteController() ->getCurrentPalette() - ->notifyColorStyleChanged(); + ->notifyColorStyleChanged(isDragging); } //============================================================================= diff --git a/toonz/sources/toonz/tpanels.h b/toonz/sources/toonz/tpanels.h index 367ce99d..a42628ee 100644 --- a/toonz/sources/toonz/tpanels.h +++ b/toonz/sources/toonz/tpanels.h @@ -133,7 +133,7 @@ public: void hide() override; protected slots: - void onColorStyleChanged(); + void onColorStyleChanged(bool); void onColorChanged(const TPixel32 &color, bool); }; diff --git a/toonz/sources/toonz/xdtsimportpopup.cpp b/toonz/sources/toonz/xdtsimportpopup.cpp index 4de865bc..2d18545c 100644 --- a/toonz/sources/toonz/xdtsimportpopup.cpp +++ b/toonz/sources/toonz/xdtsimportpopup.cpp @@ -4,6 +4,8 @@ #include "tsystem.h" #include "toonzqt/filefield.h" #include "toonz/toonzscene.h" +#include "toonz/tscenehandle.h" +#include "toonz/sceneproperties.h" #include #include @@ -11,9 +13,20 @@ #include #include #include +#include using namespace DVGui; +namespace { +QIcon getColorChipIcon(TPixel32 color) { + QPixmap pm(15, 15); + pm.fill(QColor(color.r, color.g, color.b)); + return QIcon(pm); +} +} // namespace + +//============================================================================= + XDTSImportPopup::XDTSImportPopup(QStringList levelNames, ToonzScene* scene, TFilePath scenePath) : m_scene(scene) @@ -24,6 +37,26 @@ XDTSImportPopup::XDTSImportPopup(QStringList levelNames, ToonzScene* scene, QPushButton* loadButton = new QPushButton(tr("Load"), this); QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + m_tick1Combo = new QComboBox(this); + m_tick2Combo = new QComboBox(this); + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + for (int i = 0; i < 2; i++) { + QComboBox* combo = (i == 0) ? m_tick1Combo : m_tick2Combo; + combo->addItem(tr("None"), -1); + int curId = 0; + for (auto mark : marks) { + QString label = QString("%1: %2").arg(curId).arg(mark.name); + combo->addItem(getColorChipIcon(mark.color), label, curId); + curId++; + } + } + m_tick1Combo->setCurrentIndex(m_tick1Combo->findData(0)); + m_tick2Combo->setCurrentIndex(m_tick2Combo->findData(1)); + QString description = tr("Please specify the level locations. Suggested paths " "are input in the fields with blue border."); @@ -62,6 +95,24 @@ XDTSImportPopup::XDTSImportPopup(QStringList levelNames, ToonzScene* scene, fieldsArea->setWidget(fieldsWidget); m_topLayout->addWidget(fieldsArea, 1); + // cell mark area + QGridLayout* markLay = new QGridLayout(); + markLay->setMargin(0); + markLay->setHorizontalSpacing(10); + markLay->setVerticalSpacing(10); + { + markLay->addWidget(new QLabel(tr("Inbetween symbol mark"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + markLay->addWidget(m_tick1Combo, 0, 1); + + markLay->addWidget(new QLabel(tr("Reverse sheet symbol mark"), this), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + markLay->addWidget(m_tick2Combo, 1, 1); + } + markLay->setColumnStretch(2, 1); + m_topLayout->addLayout(markLay, 0); + + connect(loadButton, SIGNAL(clicked()), this, SLOT(accept())); connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); @@ -174,8 +225,17 @@ void XDTSImportPopup::updateSuggestions(const QString samplePath) { } } +//----------------------------------------------------------------------------- + QString XDTSImportPopup::getLevelPath(QString levelName) { FileField* field = m_fields.value(levelName); if (!field) return QString(); return field->getPath(); -} \ No newline at end of file +} + +//----------------------------------------------------------------------------- + +void XDTSImportPopup::getMarkerIds(int& tick1Id, int& tick2Id) { + tick1Id = m_tick1Combo->currentData().toInt(); + tick2Id = m_tick2Combo->currentData().toInt(); +} diff --git a/toonz/sources/toonz/xdtsimportpopup.h b/toonz/sources/toonz/xdtsimportpopup.h index a185d25f..e539ee81 100644 --- a/toonz/sources/toonz/xdtsimportpopup.h +++ b/toonz/sources/toonz/xdtsimportpopup.h @@ -10,19 +10,23 @@ namespace DVGui { class FileField; } class ToonzScene; +class QComboBox; class XDTSImportPopup : public DVGui::Dialog { Q_OBJECT - QMap m_fields; + QMap m_fields; QStringList m_pathSuggestedLevels; - ToonzScene *m_scene; + ToonzScene* m_scene; + + QComboBox *m_tick1Combo, *m_tick2Combo; void updateSuggestions(const QString samplePath); public: - XDTSImportPopup(QStringList levelNames, ToonzScene *scene, + XDTSImportPopup(QStringList levelNames, ToonzScene* scene, TFilePath scenePath); QString getLevelPath(QString levelName); + void getMarkerIds(int& tick1Id, int& tick2Id); protected slots: void onPathChanged(); }; diff --git a/toonz/sources/toonz/xdtsio.cpp b/toonz/sources/toonz/xdtsio.cpp index a613f7e6..b2fc7bcc 100644 --- a/toonz/sources/toonz/xdtsio.cpp +++ b/toonz/sources/toonz/xdtsio.cpp @@ -32,10 +32,22 @@ #include #include #include +#include +#include using namespace XdtsIo; namespace { static QByteArray identifierStr("exchangeDigitalTimeSheet Save Data"); + +QIcon getColorChipIcon(TPixel32 color) { + QPixmap pm(15, 15); + pm.fill(QColor(color.r, color.g, color.b)); + return QIcon(pm); } + +int _tick1Id = -1; +int _tick2Id = -1; +bool _exportAllColumn = true; +} // namespace //----------------------------------------------------------------------------- void XdtsHeader::read(const QJsonObject &json) { QRegExp rx("\\d{1,4}"); @@ -75,7 +87,14 @@ TFrameId XdtsFrameDataItem::str2Fid(const QString &str) const { } QString XdtsFrameDataItem::fid2Str(const TFrameId &fid) const { - if (fid.getLetter().isEmpty()) return QString::number(fid.getNumber()); + if (fid.getNumber() == -1) + return QString("SYMBOL_NULL_CELL"); + else if (fid.getNumber() == SYMBOL_TICK_1) + return QString("SYMBOL_TICK_1"); + else if (fid.getNumber() == SYMBOL_TICK_2) + return QString("SYMBOL_TICK_2"); + else if (fid.getLetter().isEmpty()) + return QString::number(fid.getNumber()); return QString::number(fid.getNumber()) + fid.getLetter(); } @@ -103,11 +122,13 @@ TFrameId XdtsFrameDataItem::getFrameId() const { if (val == "SYMBOL_NULL_CELL") return TFrameId(-1); // EMPTY // ignore sheet symbols for now - else if (val == "SYMBOL_HYPHEN" || val == "SYMBOL_TICK_1" || - val == "SYMBOL_TICK_2") + else if (val == "SYMBOL_HYPHEN") return TFrameId(-2); // IGNORE - // return -1; - // return cell number + else if (val == "SYMBOL_TICK_1") + return TFrameId(SYMBOL_TICK_1); + else if (val == "SYMBOL_TICK_2") + return TFrameId(SYMBOL_TICK_2); + // return cell number return str2Fid(m_values.at(0)); } @@ -171,7 +192,8 @@ static bool frameLessThan(const QPair &v1, return v1.first < v2.first; } -QVector XdtsFieldTrackItem::getCellFrameIdTrack() const { +QVector XdtsFieldTrackItem::getCellFrameIdTrack( + QList &tick1, QList &tick2) const { QList> frameFids; for (const XdtsTrackFrameItem &frame : m_frames) frameFids.append(frame.frameFid()); @@ -195,7 +217,15 @@ QVector XdtsFieldTrackItem::getCellFrameIdTrack() const { TFrameId cellFid = frameFid.second; if (cellFid.getNumber() == -2) // IGNORE case cells.append((cells.isEmpty()) ? TFrameId(-1) : cells.last()); - else + else if (cellFid.getNumber() == + XdtsFrameDataItem::SYMBOL_TICK_1) { // SYMBOL_TICK_1 + cells.append((cells.isEmpty()) ? TFrameId(-1) : cells.last()); + tick1.append(currentFrame); + } else if (cellFid.getNumber() == + XdtsFrameDataItem::SYMBOL_TICK_2) { // SYMBOL_TICK_2 + cells.append((cells.isEmpty()) ? TFrameId(-1) : cells.last()); + tick2.append(currentFrame); + } else cells.append(cellFid); currentFrame++; } @@ -219,7 +249,14 @@ QString XdtsFieldTrackItem::build(TXshCellColumn *column) { // handle as the empty cell if (!level || cell.m_level != level) cell = TXshCell(); // continue if the cell is continuous - if (prevCell == cell) continue; + if (prevCell == cell) { + // cell mark to ticks + if (_tick1Id >= 0 && column->getCellMark(row) == _tick1Id) + addFrame(row, TFrameId(XdtsFrameDataItem::SYMBOL_TICK_1)); + else if (_tick2Id >= 0 && column->getCellMark(row) == _tick2Id) + addFrame(row, TFrameId(XdtsFrameDataItem::SYMBOL_TICK_2)); + continue; + } if (cell.isEmpty()) addFrame(row, TFrameId(-1)); @@ -268,29 +305,37 @@ QList XdtsTimeTableFieldItem::getOccupiedColumns() const { return ret; } -QVector XdtsTimeTableFieldItem::getColumnTrack(int col) const { +QVector XdtsTimeTableFieldItem::getColumnTrack( + int col, QList &tick1, QList &tick2) const { for (const XdtsFieldTrackItem &track : m_tracks) { if (track.getTrackNo() != col) continue; - return track.getCellFrameIdTrack(); + return track.getCellFrameIdTrack(tick1, tick2); } return QVector(); } void XdtsTimeTableFieldItem::build(TXsheet *xsheet, QStringList &columnLabels) { - m_fieldId = CELL; + m_fieldId = CELL; + int exportCol = 0; for (int col = 0; col < xsheet->getFirstFreeColumnIndex(); col++) { if (xsheet->isColumnEmpty(col)) { columnLabels.append(""); + exportCol++; continue; } TXshCellColumn *column = xsheet->getColumn(col)->getCellColumn(); + // skip non-cell column if (!column) { - columnLabels.append(""); continue; } - XdtsFieldTrackItem track(col); + // skip inactive column + if (!_exportAllColumn && !column->isPreviewVisible()) { + continue; + } + XdtsFieldTrackItem track(exportCol); columnLabels.append(track.build(column)); if (!track.isEmpty()) m_tracks.append(track); + exportCol++; } } //----------------------------------------------------------------------------- @@ -503,6 +548,9 @@ bool XdtsIo::loadXdtsScene(ToonzScene *scene, const TFilePath &scenePath) { return false; } + int tick1Id, tick2Id; + popup.getMarkerIds(tick1Id, tick2Id); + TXsheet *xsh = scene->getXsheet(); XdtsTimeTableFieldItem cellField = xdtsData.timeTable().getCellField(); XdtsTimeTableHeaderItem cellHeader = xdtsData.timeTable().getCellHeader(); @@ -510,9 +558,10 @@ bool XdtsIo::loadXdtsScene(ToonzScene *scene, const TFilePath &scenePath) { QStringList layerNames = cellHeader.getLayerNames(); QList columns = cellField.getOccupiedColumns(); for (int column : columns) { - QString levelName = layerNames.at(column); - TXshLevel *level = levels.value(levelName); - QVector track = cellField.getColumnTrack(column); + QString levelName = layerNames.at(column); + TXshLevel *level = levels.value(levelName); + QList tick1, tick2; + QVector track = cellField.getColumnTrack(column, tick1, tick2); int row = 0; std::vector::iterator it; @@ -530,6 +579,15 @@ bool XdtsIo::loadXdtsScene(ToonzScene *scene, const TFilePath &scenePath) { xsh->setCell(row, column, TXshCell(level, TFrameId(lastFid))); } + // set cell marks + TXshCellColumn *cellColumn = xsh->getColumn(column)->getCellColumn(); + if (tick1Id >= 0) { + for (auto tick1f : tick1) cellColumn->setCellMark(tick1f, tick1Id); + } + if (tick2Id >= 0) { + for (auto tick2f : tick2) cellColumn->setCellMark(tick2f, tick2Id); + } + TStageObject *pegbar = xsh->getStageObject(TStageObjectId::ColumnId(column)); if (pegbar) pegbar->setName(levelName.toStdString()); @@ -571,18 +629,80 @@ void ExportXDTSCommand::execute() { else duration = xsheet->getFrameCount(); - XdtsData xdtsData; - xdtsData.build(xsheet, QString::fromStdString(fp.getName()), duration); - if (xdtsData.isEmpty()) { - DVGui::error(QObject::tr("No columns can be exported.")); - return; + { + _tick1Id = -1; + _tick2Id = -1; + _exportAllColumn = true; + XdtsData pre_xdtsData; + pre_xdtsData.build(xsheet, QString::fromStdString(fp.getName()), duration); + if (pre_xdtsData.isEmpty()) { + DVGui::error(QObject::tr("No columns can be exported.")); + return; + } } static GenericSaveFilePopup *savePopup = 0; + static QComboBox *tick1Id = nullptr; + static QComboBox *tick2Id = nullptr; + static QComboBox *targetColumnCombo = nullptr; + + auto refreshCellMarkComboItems = [](QComboBox *combo) { + int current = -1; + if (combo->count()) current = combo->currentData().toInt(); + + combo->clear(); + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + combo->addItem(tr("None"), -1); + int curId = 0; + for (auto mark : marks) { + QString label = QString("%1: %2").arg(curId).arg(mark.name); + combo->addItem(getColorChipIcon(mark.color), label, curId); + curId++; + } + if (current >= 0) combo->setCurrentIndex(combo->findData(current)); + }; + if (!savePopup) { + // create custom widget + QWidget *custonWidget = new QWidget(); + tick1Id = new QComboBox(); + tick2Id = new QComboBox(); + refreshCellMarkComboItems(tick1Id); + refreshCellMarkComboItems(tick2Id); + tick1Id->setCurrentIndex(tick1Id->findData(0)); + tick2Id->setCurrentIndex(tick2Id->findData(1)); + targetColumnCombo = new QComboBox(); + targetColumnCombo->addItem(tr("All columns"), true); + targetColumnCombo->addItem(tr("Only active columns"), false); + targetColumnCombo->setCurrentIndex(targetColumnCombo->findData(true)); + + QGridLayout *customLay = new QGridLayout(); + customLay->setMargin(0); + customLay->setSpacing(10); + { + customLay->addWidget(new QLabel(tr("Inbetween symbol mark")), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + customLay->addWidget(tick1Id, 0, 1); + customLay->addWidget(new QLabel(tr("Reverse sheet symbol mark")), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + customLay->addWidget(tick2Id, 1, 1); + customLay->addWidget(new QLabel(tr("Target column")), 2, 0, + Qt::AlignRight | Qt::AlignVCenter); + customLay->addWidget(targetColumnCombo, 2, 1); + } + customLay->setColumnStretch(0, 1); + custonWidget->setLayout(customLay); + savePopup = new GenericSaveFilePopup( - QObject::tr("Export Exchange Digital Time Sheet (XDTS)")); + QObject::tr("Export Exchange Digital Time Sheet (XDTS)"), custonWidget); savePopup->addFilterType("xdts"); + } else { + refreshCellMarkComboItems(tick1Id); + refreshCellMarkComboItems(tick2Id); } if (!scene->isUntitled()) savePopup->setFolder(fp.getParentDir()); @@ -600,6 +720,16 @@ void ExportXDTSCommand::execute() { return; } + _tick1Id = tick1Id->currentData().toInt(); + _tick2Id = tick2Id->currentData().toInt(); + _exportAllColumn = targetColumnCombo->currentData().toBool(); + XdtsData xdtsData; + xdtsData.build(xsheet, QString::fromStdString(fp.getName()), duration); + if (xdtsData.isEmpty()) { + DVGui::error(QObject::tr("No columns can be exported.")); + return; + } + QJsonObject xdtsObject; xdtsData.write(xdtsObject); QJsonDocument saveDoc(xdtsObject); diff --git a/toonz/sources/toonz/xdtsio.h b/toonz/sources/toonz/xdtsio.h index d8b1df2e..846643f8 100644 --- a/toonz/sources/toonz/xdtsio.h +++ b/toonz/sources/toonz/xdtsio.h @@ -83,10 +83,11 @@ class XdtsFrameDataItem { QString fid2Str(const TFrameId &) const; public: + enum { SYMBOL_TICK_1 = -100, SYMBOL_TICK_2 = -200 }; + XdtsFrameDataItem() : m_id(Default) {} XdtsFrameDataItem(TFrameId fId) : m_id(Default) { - m_values.append((fId.getNumber() == -1) ? QString("SYMBOL_NULL_CELL") - : fid2Str(fId)); + m_values.append(fid2Str(fId)); } void read(const QJsonObject &json); void write(QJsonObject &json) const; @@ -132,7 +133,8 @@ public: void write(QJsonObject &json) const; bool isEmpty() const { return m_frames.isEmpty(); } int getTrackNo() const { return m_trackNo; } - QVector getCellFrameIdTrack() const; + QVector getCellFrameIdTrack(QList &tick1, + QList &tick2) const; QString build(TXshCellColumn *); void addFrame(int frame, TFrameId fId) { @@ -154,7 +156,8 @@ public: void write(QJsonObject &json) const; bool isCellField() { return m_fieldId == CELL; } QList getOccupiedColumns() const; - QVector getColumnTrack(int col) const; + QVector getColumnTrack(int col, QList &tick1, + QList &tick2) const; void build(TXsheet *, QStringList &); }; diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index d8ae6659..60cafc29 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -66,7 +66,6 @@ // TnzCore includes #include "tconvert.h" -#include "tundo.h" // Qt includes #include @@ -470,6 +469,14 @@ int getParamStep(TStageObject *stageObject, int frame) { //----------------------------------------------------------------------------- +QIcon getColorChipIcon(TPixel32 color) { + QPixmap pm(15, 15); + pm.fill(QColor(color.r, color.g, color.b)); + return QIcon(pm); +} + +//----------------------------------------------------------------------------- + void setParamStep(int indexKeyframe, int step, TDoubleParam *param) { KeyframeSetter setter(param, indexKeyframe); setter.setStep(step); @@ -573,6 +580,57 @@ bool isCtrlPressed = false; namespace XsheetGUI { +//============================================================================= +// SetCellMarkUndo +//----------------------------------------------------------------------------- + +SetCellMarkUndo::SetCellMarkUndo(int row, int col, int idAfter) + : m_row(row), m_col(col), m_idAfter(idAfter) { + TXshCellColumn *cellColumn = TApp::instance() + ->getCurrentXsheet() + ->getXsheet() + ->getColumn(col) + ->getCellColumn(); + assert(cellColumn); + m_idBefore = cellColumn->getCellMark(row); + if (m_idBefore == m_idAfter) m_idAfter = -1; +} + +void SetCellMarkUndo::setId(int id) const { + TXshCellColumn *cellColumn = TApp::instance() + ->getCurrentXsheet() + ->getXsheet() + ->getColumn(m_col) + ->getCellColumn(); + assert(cellColumn); + cellColumn->setCellMark(m_row, id); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); +} + +void SetCellMarkUndo::undo() const { setId(m_idBefore); } + +void SetCellMarkUndo::redo() const { setId(m_idAfter); } + +int SetCellMarkUndo::getSize() const { return sizeof *this; } + +QString SetCellMarkUndo::getHistoryString() { + QString markName; + if (m_idAfter < 0) + markName = QObject::tr("None", "Cell Mark"); + else + markName = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(m_idAfter) + .name; + return QObject::tr("Set Cell Mark at Column %1 Frame %2 to %3") + .arg(QString::number(m_col + 1)) + .arg(QString::number(m_row + 1)) + .arg(markName); +} +int SetCellMarkUndo::getHistoryType() { return HistoryType::Xsheet; } + //============================================================================= // RenameCellField //----------------------------------------------------------------------------- @@ -1160,7 +1218,7 @@ void CellArea::drawFrameSeparator(QPainter &p, int row, int col, layerAxisRange = NumberRange(layerAxis + 1, layerAxis + adjY); } - // marker interval every 6 frames + // mark interval every 6 frames int distance, offset; TApp::instance()->getCurrentScene()->getScene()->getProperties()->getMarkers( distance, offset); @@ -1503,6 +1561,24 @@ void CellArea::drawSoundCell(QPainter &p, int row, int col, bool isReference) { 1, 1, (!m_viewer->orientation()->isVerticalTimeline() && !isNextEmpty ? 2 : 0), 0); + + int markId = soundColumn->getCellMark(row); + QColor markColor; + if (markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(markId) + .color; + markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + } + QRect markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), -frameAdj.y(), + -std::round(double(frameAdj.y()) * 0.9)) + .translated(xy); + int maxNumFrame = soundColumn->getMaxFrame() + 1; int startFrame = soundColumn->getFirstRow(); TXshCell cell = soundColumn->getSoundCell(row); @@ -1522,7 +1598,15 @@ void CellArea::drawSoundCell(QPainter &p, int row, int col, bool isReference) { TXshSoundLevelP soundLevel = cell.getSoundLevel(); int r0, r1; - if (!soundColumn->getLevelRange(row, r0, r1)) return; + if (!soundColumn->getLevelRange(row, r0, r1)) { + // only draw mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } + return; + } bool isFirstRow = (row == r0); bool isLastRow = (row == r1); @@ -1658,6 +1742,13 @@ void CellArea::drawSoundCell(QPainter &p, int row, int col, bool isReference) { p.setPen(m_viewer->getMarkerLineColor()); p.drawLine(o->line(PredefinedLine::SEE_MARKER_THROUGH).translated(xy)); } + + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } } //----------------------------------------------------------------------------- @@ -1770,10 +1861,25 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, TXshCell cell = xsh->getCell(row, col); TXshCell prevCell; + TXshCellColumn *cellColumn = xsh->getColumn(col)->getCellColumn(); + int markId = (cellColumn) ? cellColumn->getCellMark(row) : -1; + QColor markColor; + if (markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(markId) + .color; + markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + } + TCellSelection *cellSelection = m_viewer->getCellSelection(); TColumnSelection *columnSelection = m_viewer->getColumnSelection(); bool isSelected = cellSelection->isCellSelected(row, col) || columnSelection->isColumnSelected(col); + bool isSimpleView = m_viewer->getFrameZoomFactor() <= + o->dimension(PredefinedDimension::SCALE_THRESHOLD); if (row > 0) prevCell = xsh->getCell(row - 1, col); // cell in previous frame @@ -1798,11 +1904,16 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, .translated(QPoint(x, y)); cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); QRect rect = cellRect.adjusted( - 1, 1, - (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() - ? 2 - : 0), - 0); + 1, 1, (!o->isVerticalTimeline() && !nextCell.isEmpty() ? 2 : 0), 0); + + QRect markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), -frameAdj.y(), + -std::round(double(frameAdj.y()) * 0.9)) + .translated(xy); + if (showLevelName && (!isSimpleView || !o->isVerticalTimeline())) + markRect.moveCenter(cellRect.center()); + if (markRect.right() > rect.right()) markRect.setRight(rect.right()); // get cell colors QColor cellColor, sideColor; @@ -1831,6 +1942,14 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, p.fillRect(rect, QBrush(cellColor)); } + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + // p.fillRect(rect, QBrush(markColor)); + } + drawFrameSeparator(p, row, col, true); if (TApp::instance()->getCurrentFrame()->isEditingScene() && @@ -1847,6 +1966,14 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, if (cell.isEmpty()) { // it means previous is not empty // diagonal cross meaning end of level + + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } + QColor levelEndColor = m_viewer->getTextColor(); levelEndColor.setAlphaF(0.3); p.setPen(levelEndColor); @@ -1890,14 +2017,6 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, else p.fillRect(rect, QBrush(cellColor)); - if (TApp::instance()->getCurrentFrame()->isEditingScene() && - !m_viewer->orientation()->isVerticalTimeline() && - row == m_viewer->getCurrentRow() && - Preferences::instance()->isCurrentTimelineIndicatorEnabled()) - drawCurrentTimeIndicator(p, xy); - - drawDragHandle(p, xy, sideColor); - if (yetToCleanupCell) // ORIENTATION: what's this? { if (o->isVerticalTimeline()) @@ -1910,6 +2029,14 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, : m_viewer->getFullcolorColumnColor()); } + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !m_viewer->orientation()->isVerticalTimeline() && + row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) + drawCurrentTimeIndicator(p, xy); + + drawDragHandle(p, xy, sideColor); + bool isLastRow = nextCell.isEmpty() || cell.m_level.getPointer() != nextCell.m_level.getPointer(); drawEndOfDragHandle(p, isLastRow, xy, cellColor); @@ -1922,8 +2049,6 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, // if (distance == 0) distance = 6; bool isAfterMarkers = distance > 0 && ((row - offset) % distance) == 0 && row != 0; - bool isSimpleView = m_viewer->getFrameZoomFactor() <= - o->dimension(PredefinedDimension::SCALE_THRESHOLD); // draw marker interval if (o->isVerticalTimeline() && isAfterMarkers) { @@ -1973,27 +2098,36 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, // draw continue line QString fnum; if (sameLevel && prevCell.m_frameId == cell.m_frameId) { - if (!o->isVerticalTimeline()) return; + if (o->isVerticalTimeline()) { + // not on line marker + PredefinedLine which = + Preferences::instance()->isLevelNameOnEachMarkerEnabled() + ? PredefinedLine::CONTINUE_LEVEL_WITH_NAME + : PredefinedLine::CONTINUE_LEVEL; - // not on line marker - PredefinedLine which = - Preferences::instance()->isLevelNameOnEachMarkerEnabled() - ? PredefinedLine::CONTINUE_LEVEL_WITH_NAME - : PredefinedLine::CONTINUE_LEVEL; + QLine continueLine = o->line(which).translated(xy); + continueLine.setP2(QPoint(continueLine.x2(), continueLine.y2()) - + frameAdj); - QLine continueLine = o->line(which).translated(xy); - continueLine.setP2(QPoint(continueLine.x2(), continueLine.y2()) - frameAdj); + if (!showLevelName) { + penColor.setAlphaF(0.5); + p.setPen(penColor); + } - if (!showLevelName) { - penColor.setAlphaF(0.5); - p.setPen(penColor); + p.drawLine(continueLine); } - - p.drawLine(continueLine); } // draw frame number else { if (isSimpleView) { + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + p.setPen(penColor); + } + if (!o->isVerticalTimeline()) { // Lets not draw normal marker if there is a keyframe here TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col)); @@ -2024,6 +2158,14 @@ void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference, p.drawText(nameRect, alignFlag, fnum); } + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + p.setPen(penColor); + } + // draw level name if (showLevelName && (!sameLevel || @@ -2072,6 +2214,34 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { xy.setX(xy.x() + 1); } + TXshCell nextCell = xsh->getCell(row + 1, col); + QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); + QRect cellRect = o->rect(PredefinedRect::CELL).translated(QPoint(x, y)); + cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); + QRect rect = cellRect.adjusted( + 1, 1, + (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() + ? 2 + : 0), + 0); + int markId = xsh->getColumn(col)->getCellColumn()->getCellMark(row); + QColor markColor; + if (markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(markId) + .color; + markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + } + QRect markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), -frameAdj.y(), + -std::round(double(frameAdj.y()) * 0.9)) + .translated(xy); + if (markRect.right() > rect.right()) markRect.setRight(rect.right()); + if (cell.isEmpty() && prevCell.isEmpty()) { drawFrameSeparator(p, row, col, true); if (TApp::instance()->getCurrentFrame()->isEditingScene() && @@ -2087,18 +2257,6 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { prevCell.m_frameId == cell.m_frameId); drawFrameSeparator(p, row, col, false, heldFrame); - TXshCell nextCell; - nextCell = xsh->getCell(row + 1, col); - - QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); - QRect cellRect = o->rect(PredefinedRect::CELL).translated(QPoint(x, y)); - cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); - QRect rect = cellRect.adjusted( - 1, 1, - (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() - ? 2 - : 0), - 0); if (cell.isEmpty()) { // diagonal cross meaning end of level QColor levelEndColor = m_viewer->getTextColor(); levelEndColor.setAlphaF(0.3); @@ -2106,12 +2264,24 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { p.drawLine(rect.topLeft(), rect.bottomRight()); p.drawLine(rect.topRight(), rect.bottomLeft()); + // only draw mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } if (TApp::instance()->getCurrentFrame()->isEditingScene() && !m_viewer->orientation()->isVerticalTimeline() && row == m_viewer->getCurrentRow() && Preferences::instance()->isCurrentTimelineIndicatorEnabled()) drawCurrentTimeIndicator(p, xy); + // only draw mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } return; } @@ -2143,6 +2313,13 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { drawLockedDottedLine(p, xsh->getColumn(col)->isLocked(), xy, cellColor); + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } + TFrameId fid = cell.m_frameId; if (fid.getNumber() - 1 < 0) return; @@ -2238,6 +2415,9 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, TXshPaletteLevel *pl = cell.getPaletteLevel(); if (pl && !pl->getPalette()) isRed = true; + bool isSimpleView = m_viewer->getFrameZoomFactor() <= + o->dimension(PredefinedDimension::SCALE_THRESHOLD); + QPoint xy = m_viewer->positionToXY(CellPosition(row, col)); int x = xy.x(); int y = xy.y(); @@ -2248,6 +2428,35 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, xy.setX(xy.x() + 1); } + QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); + QRect cellRect = o->rect(PredefinedRect::CELL).translated(QPoint(x, y)); + cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); + QRect rect = cellRect.adjusted( + 1, 1, + (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() + ? 2 + : 0), + 0); + int markId = xsh->getColumn(col)->getCellColumn()->getCellMark(row); + QColor markColor; + if (markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(markId) + .color; + markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + } + QRect markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), -frameAdj.y(), + -std::round(double(frameAdj.y()) * 0.9)) + .translated(xy); + if (!isSimpleView || !o->isVerticalTimeline()) + markRect.moveCenter(cellRect.center()); + if (markRect.right() > rect.right()) markRect.setRight(rect.right()); + if (cell.isEmpty() && prevCell.isEmpty()) { drawFrameSeparator(p, row, col, true); if (TApp::instance()->getCurrentFrame()->isEditingScene() && @@ -2263,15 +2472,6 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, prevCell.m_frameId == cell.m_frameId); drawFrameSeparator(p, row, col, false, heldFrame); - QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); - QRect cellRect = o->rect(PredefinedRect::CELL).translated(QPoint(x, y)); - cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); - QRect rect = cellRect.adjusted( - 1, 1, - (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() - ? 2 - : 0), - 0); if (cell.isEmpty()) { // this means the former is not empty QColor levelEndColor = m_viewer->getTextColor(); levelEndColor.setAlphaF(0.3); @@ -2279,12 +2479,24 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, p.drawLine(rect.topLeft(), rect.bottomRight()); p.drawLine(rect.topRight(), rect.bottomLeft()); + // only draw mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } if (TApp::instance()->getCurrentFrame()->isEditingScene() && !m_viewer->orientation()->isVerticalTimeline() && row == m_viewer->getCurrentRow() && Preferences::instance()->isCurrentTimelineIndicatorEnabled()) drawCurrentTimeIndicator(p, xy); + // only draw mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } return; } @@ -2328,16 +2540,24 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, if (sameLevel && prevCell.m_frameId == cell.m_frameId && !isAfterMarkers) { // cell equal to previous one (not on marker line): // do not write anything and draw a vertical line - if (!o->isVerticalTimeline()) return; - QPen oldPen = p.pen(); - p.setPen(QPen(m_viewer->getTextColor(), 1)); - QLine continueLine = o->line(PredefinedLine::CONTINUE_LEVEL).translated(xy); - continueLine.setP2(QPoint(continueLine.x2(), continueLine.y2()) - frameAdj); - p.drawLine(continueLine); - p.setPen(oldPen); + if (o->isVerticalTimeline()) { + QPen oldPen = p.pen(); + p.setPen(QPen(m_viewer->getTextColor(), 1)); + QLine continueLine = + o->line(PredefinedLine::CONTINUE_LEVEL).translated(xy); + continueLine.setP2(QPoint(continueLine.x2(), continueLine.y2()) - + frameAdj); + p.drawLine(continueLine); + p.setPen(oldPen); + } } else { - if (m_viewer->getFrameZoomFactor() <= - o->dimension(PredefinedDimension::SCALE_THRESHOLD)) { + if (isSimpleView) { + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } if (!o->isVerticalTimeline()) { // Lets not draw normal marker if there is a keyframe here TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col)); @@ -2415,6 +2635,13 @@ void CellArea::drawPaletteCell(QPainter &p, int row, int col, if (!sameLevel || isAfterMarkers) p.drawText(nameRect, Qt::AlignLeft | Qt::AlignBottom, elidaName); } + + // cell mark + if (markId >= 0) { + p.setBrush(markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(markRect); + } } //----------------------------------------------------------------------------- @@ -3267,13 +3494,13 @@ void CellArea::contextMenuEvent(QContextMenuEvent *event) { } if (areCellsEmpty) break; } - createCellMenu(menu, areCellsEmpty, cell); + createCellMenu(menu, areCellsEmpty, cell, row, col); } else { m_viewer->getCellSelection()->makeCurrent(); m_viewer->getCellSelection()->selectCell(row, col); m_viewer->setCurrentColumn(col); - createCellMenu(menu, !cell.isEmpty(), cell); + createCellMenu(menu, !cell.isEmpty(), cell, row, col); } if (!menu.isEmpty()) menu.exec(event->globalPos()); @@ -3362,7 +3589,8 @@ void CellArea::onControlPressed(bool pressed) { const bool CellArea::isControlPressed() { return isCtrlPressed; } //----------------------------------------------------------------------------- -void CellArea::createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell) { +void CellArea::createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell, + int row, int col) { CommandManager *cmdManager = CommandManager::instance(); bool soundCellsSelected = m_viewer->areSoundCellsSelected(); @@ -3550,10 +3778,21 @@ void CellArea::createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell) { } else if (selectionContainTlvImage(m_viewer->getCellSelection(), m_viewer->getXsheet())) menu.addAction(cmdManager->getAction(MI_CanvasSize)); - if (sl || - (TApp::instance()->getCurrentLevel()->getLevel() && - TApp::instance()->getCurrentLevel()->getLevel()->getChildLevel())) - menu.addAction(cmdManager->getAction(MI_LipSyncPopup)); + + QMenu *lipSyncMenu = new QMenu(tr("Lip Sync"), this); + { + if (sl || + (TApp::instance()->getCurrentLevel()->getLevel() && + TApp::instance()->getCurrentLevel()->getLevel()->getChildLevel())) + lipSyncMenu->addAction(cmdManager->getAction(MI_LipSyncPopup)); + if (!soundCellsSelected) + lipSyncMenu->addAction(cmdManager->getAction(MI_ImportMagpieFile)); + } + if (lipSyncMenu->actions().isEmpty()) + delete lipSyncMenu; + else + menu.addMenu(lipSyncMenu); + } else { menu.addAction(cmdManager->getAction(MI_CreateBlankDrawing)); menu.addSeparator(); @@ -3565,13 +3804,44 @@ void CellArea::createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell) { menu.addAction(cmdManager->getAction(MI_ShiftKeyframesUp)); } } + + // cell mark menu + TXshCellColumn *cellColumn = + (m_viewer->getXsheet()->getColumn(col)) + ? m_viewer->getXsheet()->getColumn(col)->getCellColumn() + : nullptr; + if (cellColumn) { + QMenu *marksMenu = new QMenu(tr("Cell Mark"), this); + int markId = cellColumn->getCellMark(row); + QAction *markAction = marksMenu->addAction(tr("None")); + markAction->setCheckable(true); + markAction->setChecked(markId == -1); + markAction->setEnabled(markId != -1); + markAction->setData(QList{row, col, -1}); + connect(markAction, SIGNAL(triggered()), this, SLOT(onSetCellMark())); + QList marks = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMarks(); + int curId = 0; + for (auto mark : marks) { + QString label = QString("%1: %2").arg(curId).arg(mark.name); + markAction = marksMenu->addAction(getColorChipIcon(mark.color), label); + markAction->setCheckable(true); + markAction->setChecked(markId == curId); + markAction->setEnabled(markId != curId); + markAction->setData(QList{row, col, curId}); + connect(markAction, SIGNAL(triggered()), this, SLOT(onSetCellMark())); + curId++; + } + + menu.addMenu(marksMenu); + } menu.addSeparator(); menu.addAction(cmdManager->getAction(MI_ToggleAutoCreate)); menu.addAction(cmdManager->getAction(MI_ToggleCreationInHoldCells)); menu.addAction(cmdManager->getAction(MI_ToggleAutoStretch)); - menu.addSeparator(); - if (!soundCellsSelected) - menu.addAction(cmdManager->getAction(MI_ImportMagpieFile)); } //----------------------------------------------------------------------------- @@ -3835,4 +4105,19 @@ void CellArea::updateCursor() { //----------------------------------------------------------------------------- +void CellArea::onSetCellMark() { + QAction *senderAction = qobject_cast(sender()); + assert(senderAction); + QList params = senderAction->data().toList(); + assert(params.count() == 3); + int row = params[0].toInt(); + int col = params[1].toInt(); + int id = params[2].toInt(); + SetCellMarkUndo *undo = new SetCellMarkUndo(row, col, id); + undo->redo(); + TUndoManager::manager()->add(undo); +} + +//----------------------------------------------------------------------------- + } // namespace XsheetGUI diff --git a/toonz/sources/toonz/xshcellviewer.h b/toonz/sources/toonz/xshcellviewer.h index d741de8f..1b3e3e9a 100644 --- a/toonz/sources/toonz/xshcellviewer.h +++ b/toonz/sources/toonz/xshcellviewer.h @@ -8,6 +8,7 @@ #include "orientation.h" #include "toonz/txshcell.h" +#include "tundo.h" // forward declaration class XsheetViewer; @@ -17,6 +18,20 @@ class TXshSoundTextColumn; namespace XsheetGUI { +class SetCellMarkUndo final : public TUndo { + int m_row, m_col; + int m_idBefore, m_idAfter; + +public: + SetCellMarkUndo(int row, int col, int idAfter); + void setId(int id) const; + void undo() const override; + void redo() const override; + int getSize() const override; + QString getHistoryString() override; + int getHistoryType() override; +}; + class NoteWidget; class DragTool; @@ -166,7 +181,8 @@ protected: /*!Crea il menu' del tasto destro che si visualizza quando si clicca sulla cella, distinguendo i due casi: cella piena, cella vuota.*/ - void createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell); + void createCellMenu(QMenu &menu, bool isCellSelected, TXshCell cell, int row, + int col); //! Crea il menu' del tasto destro che si visualizza si clicca su un key //! frame. void createKeyMenu(QMenu &menu); @@ -182,6 +198,7 @@ protected slots: void onStepChanged(QAction *); // replace level with another level in the cast void onReplaceByCastedLevel(QAction *action); + void onSetCellMark(); }; } // namespace XsheetGUI diff --git a/toonz/sources/toonz/xsheetcmd.cpp b/toonz/sources/toonz/xsheetcmd.cpp index 00cc38e8..65c76ee6 100644 --- a/toonz/sources/toonz/xsheetcmd.cpp +++ b/toonz/sources/toonz/xsheetcmd.cpp @@ -60,6 +60,7 @@ #include "duplicatepopup.h" #include "menubarcommandids.h" #include "columncommand.h" +#include "xshcellviewer.h" // SetCellMarkUndo // Qt includes #include @@ -2199,6 +2200,43 @@ public: } ToggleXsheetCameraColumnCommand; +//----------------------------------------------------------------------------- + +class SetCellMarkCommand final : public MenuItemHandler { + int m_markId; + +public: + SetCellMarkCommand(int markId) + : MenuItemHandler( + ((std::string)MI_SetCellMark + std::to_string(markId)).c_str()) + , m_markId(markId) {} + + void execute() override { + TApp *app = TApp::instance(); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + int currentRow = app->getCurrentFrame()->getFrame(); + int currentColumn = app->getCurrentColumn()->getColumnIndex(); + if (!xsh->getColumn(currentColumn)) return; + TXshCellColumn *cellColumn = xsh->getColumn(currentColumn)->getCellColumn(); + if (!cellColumn) return; + XsheetGUI::SetCellMarkUndo *undo = + new XsheetGUI::SetCellMarkUndo(currentRow, currentColumn, m_markId); + undo->redo(); + TUndoManager::manager()->add(undo); + } +}; +SetCellMarkCommand CellMarkCommand0(0); +SetCellMarkCommand CellMarkCommand1(1); +SetCellMarkCommand CellMarkCommand2(2); +SetCellMarkCommand CellMarkCommand3(3); +SetCellMarkCommand CellMarkCommand4(4); +SetCellMarkCommand CellMarkCommand5(5); +SetCellMarkCommand CellMarkCommand6(6); +SetCellMarkCommand CellMarkCommand7(7); +SetCellMarkCommand CellMarkCommand8(8); +SetCellMarkCommand CellMarkCommand9(9); +SetCellMarkCommand CellMarkCommand10(10); +SetCellMarkCommand CellMarkCommand11(11); //============================================================ diff --git a/toonz/sources/toonzlib/orientation.cpp b/toonz/sources/toonzlib/orientation.cpp index e7e3eb1a..0b21e6db 100644 --- a/toonz/sources/toonzlib/orientation.cpp +++ b/toonz/sources/toonzlib/orientation.cpp @@ -335,6 +335,9 @@ TopToBottomOrientation::TopToBottomOrientation() { addRect(PredefinedRect::KEYFRAME_AREA, QRect(CELL_WIDTH - KEY_ICON_WIDTH, 0, KEY_ICON_WIDTH, CELL_HEIGHT)); addRect(PredefinedRect::DRAG_AREA, QRect(0, 0, CELL_DRAG_WIDTH, CELL_HEIGHT)); + int markSize = CELL_HEIGHT * 8 / 10; // 80% size + addRect(PredefinedRect::CELL_MARK_AREA, + QRect(CELL_DRAG_WIDTH - 3, 2, markSize, markSize)); QRect soundRect(CELL_DRAG_WIDTH, 0, CELL_WIDTH - CELL_DRAG_WIDTH - SOUND_PREVIEW_WIDTH, CELL_HEIGHT); @@ -1111,6 +1114,9 @@ LeftToRightOrientation::LeftToRightOrientation() { EXTENDER_HEIGHT)); addRect(PredefinedRect::KEYFRAME_AREA, keyRect); addRect(PredefinedRect::DRAG_AREA, QRect(0, 0, CELL_WIDTH, CELL_DRAG_HEIGHT)); + int markSize = CELL_HEIGHT / 2; // 50% size (12px) + addRect(PredefinedRect::CELL_MARK_AREA, + QRect(1, CELL_DRAG_HEIGHT + 1, markSize, markSize)); QRect soundRect(0, CELL_DRAG_HEIGHT, CELL_WIDTH, CELL_HEIGHT - CELL_DRAG_HEIGHT - SOUND_PREVIEW_HEIGHT); addRect(PredefinedRect::SOUND_TRACK, soundRect); diff --git a/toonz/sources/toonzlib/sceneproperties.cpp b/toonz/sources/toonzlib/sceneproperties.cpp index 59c71c8e..88b7495e 100644 --- a/toonz/sources/toonzlib/sceneproperties.cpp +++ b/toonz/sources/toonzlib/sceneproperties.cpp @@ -22,6 +22,23 @@ #include "tproperty.h" #include "tiio.h" +namespace { +const TSceneProperties::CellMark cellMarkDefault[12] = { + {QObject::tr("Red"), TPixel32(167, 55, 55)}, + {QObject::tr("Orange"), TPixel32(195, 115, 40)}, + {QObject::tr("Yellow"), TPixel32(214, 183, 22)}, + {QObject::tr("Light Green"), TPixel32(165, 179, 57)}, + {QObject::tr("Green"), TPixel32(82, 157, 79)}, + {QObject::tr("Light Blue"), TPixel32(71, 142, 165)}, + {QObject::tr("Blue"), TPixel32(64, 103, 172)}, + {QObject::tr("Dark Blue"), TPixel32(60, 49, 187)}, + {QObject::tr("Purple"), TPixel32(108, 66, 170)}, + {QObject::tr("Pink"), TPixel32(161, 75, 140)}, + {QObject::tr("Dark Pink"), TPixel32(111, 29, 108)}, + {QObject::tr("White"), TPixel32(255, 255, 255)}}; + +} + //============================================================================= TSceneProperties::TSceneProperties() @@ -47,6 +64,9 @@ TSceneProperties::TSceneProperties() m_notesColor.push_back(TPixel32(145, 240, 145)); m_notesColor.push_back(TPixel32(130, 255, 210)); m_notesColor.push_back(TPixel32(150, 245, 255)); + + // Default Cell Marks + for (int i = 0; i < 12; i++) m_cellMarks.push_back(cellMarkDefault[i]); } //----------------------------------------------------------------------------- @@ -77,7 +97,7 @@ void TSceneProperties::assign(const TSceneProperties *sprop) { if (sprop != this) { m_cameras = sprop->m_cameras; - for (int i = 0; i < (int)m_cameras.size(); i++) + for (int i = 0; i < (int)m_cameras.size(); i++) m_cameras[i] = new TCamera(*m_cameras[i]); } m_bgColor = sprop->m_bgColor; @@ -142,7 +162,7 @@ void TSceneProperties::setFieldGuideSize(int size) { void TSceneProperties::setFieldGuideAspectRatio(double ar) { assert(ar >= 0); - if (ar <= 0) ar = 1; + if (ar <= 0) ar = 1; m_fieldGuideAspectRatio = ar; } @@ -196,8 +216,8 @@ void TSceneProperties::saveData(TOStream &os) const { os.child("threadsIndex") << out.getThreadIndex(); os.child("maxTileSizeIndex") << out.getMaxTileSizeIndex(); os.child("subcameraPrev") << (out.isSubcameraPreview() ? 1 : 0); - os.child("stereoscopic") << (rs.m_stereoscopic ? 1 : 0) - << rs.m_stereoscopicShift; + os.child("stereoscopic") + << (rs.m_stereoscopic ? 1 : 0) << rs.m_stereoscopicShift; switch (rs.m_quality) { case TRenderSettings::StandardResampleQuality: @@ -313,6 +333,12 @@ void TSceneProperties::saveData(TOStream &os) const { os.openChild("noteColors"); for (i = 0; i < m_notesColor.size(); i++) os << m_notesColor.at(i); os.closeChild(); + + if (!hasDefaultCellMarks()) { + os.openChild("cellMarks"); + for (auto mark : m_cellMarks) os << mark.name.toStdString() << mark.color; + os.closeChild(); + } } //----------------------------------------------------------------------------- @@ -413,7 +439,7 @@ void TSceneProperties::loadData(TIStream &is, bool isLoadingProject) { if (name == "preview") outPtr = m_previewProp; else if (name == "main") - outPtr = m_outputProp; + outPtr = m_outputProp; TOutputProperties &out = *outPtr; TRenderSettings renderSettings; if (globFrom != -1) @@ -697,6 +723,15 @@ void TSceneProperties::loadData(TIStream &is, bool isLoadingProject) { assert(i == 7); } else if (tagName == "cameraCaputureSaveInPath") { is >> m_camCapSaveInPath; + } else if (tagName == "cellMarks") { + int i = 0; + while (!is.eos()) { + TPixel32 color; + std::string name; + is >> name >> color; + m_cellMarks.replace(i, {QString::fromStdString(name), color}); + i++; + } } else { throw TException("unexpected property tag: " + tagName); } @@ -770,3 +805,34 @@ TPixel32 TSceneProperties::getNoteColor(int colorIndex) const { void TSceneProperties::setNoteColor(TPixel32 color, int colorIndex) { m_notesColor[colorIndex] = color; } + +//----------------------------------------------------------------------------- + +QList TSceneProperties::getCellMarks() const { + return m_cellMarks; +} + +//----------------------------------------------------------------------------- + +TSceneProperties::CellMark TSceneProperties::getCellMark(int index) const { + return m_cellMarks[index]; +} + +//----------------------------------------------------------------------------- + +void TSceneProperties::setCellMark(const TSceneProperties::CellMark &mark, + int index) { + m_cellMarks[index] = mark; +} + +//----------------------------------------------------------------------------- +// check if the cell mark settings are modified +bool TSceneProperties::hasDefaultCellMarks() const { + if (m_cellMarks.size() != 12) return false; + for (int i = 0; i < 12; i++) { + if (m_cellMarks.at(i).name != cellMarkDefault[i].name || + m_cellMarks.at(i).color != cellMarkDefault[i].color) + return false; + } + return true; +} \ No newline at end of file diff --git a/toonz/sources/toonzlib/txshcolumn.cpp b/toonz/sources/toonzlib/txshcolumn.cpp index 2f4f6c7d..df5dcad5 100644 --- a/toonz/sources/toonzlib/txshcolumn.cpp +++ b/toonz/sources/toonzlib/txshcolumn.cpp @@ -19,6 +19,7 @@ #include "toonz/txshleveltypes.h" #include +#include "tstream.h" namespace { QMap> filterColors; @@ -144,12 +145,12 @@ void TXshCellColumn::getCells(int row, int rowCount, TXshCell cells[]) { } if (n + src > cellCount) n = cellCount - src; - TXshCell *dstCell = cells; - TXshCell *endDstCell = dstCell + dst; + TXshCell *dstCell = cells; + TXshCell *endDstCell = dstCell + dst; while (dstCell < endDstCell) *dstCell++ = emptyCell; endDstCell += n; while (dstCell < endDstCell) *dstCell++ = m_cells[src++]; - endDstCell = cells + rowCount; + endDstCell = cells + rowCount; while (dstCell < endDstCell) *dstCell++ = emptyCell; } @@ -243,7 +244,7 @@ bool TXshCellColumn::setCells(int row, int rowCount, const TXshCell cells[]) { if (row > c_rb) // sono oltre l'ultima riga { if (oldCellCount == 0) m_first = row; // row 'e la nuova firstrow - int newCellCount = row - m_first + rowCount; + int newCellCount = row - m_first + rowCount; m_cells.resize(newCellCount); } else if (row < m_first) { int delta = m_first - row; @@ -278,8 +279,9 @@ bool TXshCellColumn::setCells(int row, int rowCount, const TXshCell cells[]) { //----------------------------------------------------------------------------- void TXshCellColumn::insertEmptyCells(int row, int rowCount) { - if (m_cells.empty()) return; // se la colonna e' vuota non devo inserire - // celle + if (m_cells.empty()) + return; // se la colonna e' vuota non devo inserire + // celle if (row >= m_first + (int)m_cells.size()) return; // dopo:non inserisco nulla if (row <= m_first) // prima @@ -426,6 +428,70 @@ bool TXshCellColumn::getLevelRange(int row, int &r0, int &r1) const { return true; } +//----------------------------------------------------------------------------- + +void TXshCellColumn::saveCellMarks(TOStream &os) { + if (m_cellMarkIds.isEmpty()) return; + // gather frame numbers with the same id + QMap idStrMap; + QMap::const_iterator i = m_cellMarkIds.constBegin(); + while (i != m_cellMarkIds.constEnd()) { + if (!idStrMap.contains(i.value())) + idStrMap.insert(i.value(), QString::number(i.key())); + else + idStrMap[i.value()] += " " + QString::number(i.key()); + ++i; + } + os.openChild("cellMarks"); + QMap::const_iterator j = idStrMap.constBegin(); + while (j != idStrMap.constEnd()) { + std::map attr; + attr["id"] = std::to_string(j.key()); + os.openChild("cellMark", attr); + os << j.value(); + os.closeChild(); + ++j; + } + os.closeChild(); +} + +bool TXshCellColumn::loadCellMarks(std::string tagName, TIStream &is) { + if (tagName != "cellMarks") return false; + m_cellMarkIds.clear(); + while (is.openChild(tagName)) { + if (tagName == "cellMark") { + int id; + QString frameStr; + if (is.getTagParam("id", id)) { + is >> frameStr; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QStringList frameStrList = frameStr.split(" ", Qt::SkipEmptyParts); +#else + QStringList frameStrList = frameStr.split(" ", QString::SkipEmptyParts); +#endif + for (auto fStr : frameStrList) m_cellMarkIds.insert(fStr.toInt(), id); + } + } + is.closeChild(); + } + return true; +} + +void TXshCellColumn::setCellMark(int frame, int id) { + if (id < 0) + m_cellMarkIds.remove(frame); + else + m_cellMarkIds.insert(frame, id); +} + +int TXshCellColumn::getCellMark(int frame) const { + return m_cellMarkIds.value(frame, -1); +} + +QMap TXshCellColumn::getCellMarks() const { return m_cellMarkIds; } + +void TXshCellColumn::clearCellMarks() { m_cellMarkIds.clear(); } + //============================================================================= // TXshColumn diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index db9425c8..4844f7da 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -1222,6 +1222,12 @@ void TXsheet::loadData(TIStream &is) { } } } + } else if (tagName == "cameraColumn") { + while (is.openChild(tagName)) { + if (!m_cameraColumn->getCellColumn()->loadCellMarks(tagName, is)) + throw TException("Camera Column, unknown tag: " + tagName); + is.closeChild(); + } } else if (tagName == "pegbars") { TPersist *p = m_imp->m_pegTree; m_imp->m_pegTree->loadData(is, this); @@ -1278,6 +1284,14 @@ void TXsheet::saveData(TOStream &os) { if (column && c < getFirstFreeColumnIndex()) os << column.getPointer(); } os.closeChild(); + + // save cell marks in the camera column + if (!m_cameraColumn->getCellColumn()->getCellMarks().isEmpty()) { + os.openChild("cameraColumn"); + m_cameraColumn->getCellColumn()->saveCellMarks(os); + os.closeChild(); + } + os.openChild("pegbars"); m_imp->m_pegTree->saveData(os, getFirstFreeColumnIndex(), this); // os << *(m_imp->m_pegTree); diff --git a/toonz/sources/toonzlib/txshlevelcolumn.cpp b/toonz/sources/toonzlib/txshlevelcolumn.cpp index affee586..609c93ad 100644 --- a/toonz/sources/toonzlib/txshlevelcolumn.cpp +++ b/toonz/sources/toonzlib/txshlevelcolumn.cpp @@ -151,9 +151,10 @@ void TXshLevelColumn::loadData(TIStream &is) { { TFxSet fxSet; fxSet.loadData(is); - } else { + } else if (loadCellMarks(tagName, is)) { + // do nothing + } else throw TException("TXshLevelColumn, unknown tag: " + tagName); - } is.closeChild(); } } @@ -201,6 +202,9 @@ void TXshLevelColumn::saveData(TOStream &os) { os.closeChild(); } os.child("fx") << m_fx; + + // cell marks + saveCellMarks(os); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/txshmeshcolumn.cpp b/toonz/sources/toonzlib/txshmeshcolumn.cpp index 76302db2..7e0080d1 100644 --- a/toonz/sources/toonzlib/txshmeshcolumn.cpp +++ b/toonz/sources/toonzlib/txshmeshcolumn.cpp @@ -111,6 +111,8 @@ void TXshMeshColumn::saveData(TOStream &os) { } os.closeChild(); } + // cell marks + saveCellMarks(os); } //------------------------------------------------------------------ @@ -167,6 +169,8 @@ void TXshMeshColumn::loadData(TIStream &is) { is.skipCurrentTag(); } + is.closeChild(); + } else if (loadCellMarks(tagName, is)) { is.closeChild(); } else is.skipCurrentTag(); diff --git a/toonz/sources/toonzlib/txshpalettecolumn.cpp b/toonz/sources/toonzlib/txshpalettecolumn.cpp index 45203d62..2c377263 100644 --- a/toonz/sources/toonzlib/txshpalettecolumn.cpp +++ b/toonz/sources/toonzlib/txshpalettecolumn.cpp @@ -73,6 +73,8 @@ void TXshPaletteColumn::loadData(TIStream &is) { TPersist *p = 0; is >> p; if (TFx *fx = dynamic_cast(p)) setFx(fx); + } else if (loadCellMarks(tagName, is)) { + // do nothing } else { throw TException("TXshLevelColumn, unknown tag: " + tagName); } @@ -107,6 +109,9 @@ void TXshPaletteColumn::saveData(TOStream &os) { os.closeChild(); } os.child("fx") << m_fx; + + // cell marks + saveCellMarks(os); } PERSIST_IDENTIFIER(TXshPaletteColumn, "paletteColumn") diff --git a/toonz/sources/toonzlib/txshsoundcolumn.cpp b/toonz/sources/toonzlib/txshsoundcolumn.cpp index 89f96f07..53eeb748 100644 --- a/toonz/sources/toonzlib/txshsoundcolumn.cpp +++ b/toonz/sources/toonzlib/txshsoundcolumn.cpp @@ -262,6 +262,13 @@ void TXshSoundColumn::loadData(TIStream &is) { is >> status; setStatusWord(status); } + + std::string tagName; + while (is.openChild(tagName)) { + if (!loadCellMarks(tagName, is)) + throw TException("TXshLevelColumn, unknown tag: " + tagName); + is.closeChild(); + } } //----------------------------------------------------------------------------- @@ -274,6 +281,8 @@ void TXshSoundColumn::saveData(TOStream &os) { int i; for (i = 0; i < levelsCount; i++) m_levels.at(i)->saveData(os); os << getStatusWord(); + // cell marks + saveCellMarks(os); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp index 163fec38..65e47526 100644 --- a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp +++ b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp @@ -80,6 +80,8 @@ void TXshSoundTextColumn::loadData(TIStream &is) { throw TException("TXshLevelColumn, unknown tag(2): " + tagName); is.closeChild(); } + } else if (loadCellMarks(tagName, is)) { + // do nothing } else throw TException("TXshLevelColumn, unknown tag: " + tagName); is.closeChild(); @@ -100,6 +102,8 @@ void TXshSoundTextColumn::saveData(TOStream &os) { } os.closeChild(); } + // cell marks + saveCellMarks(os); } PERSIST_IDENTIFIER(TXshSoundTextColumn, "soundTextColumn") diff --git a/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp b/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp index b7940d50..17bf05a4 100644 --- a/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp +++ b/toonz/sources/toonzlib/txshzeraryfxcolumn.cpp @@ -162,6 +162,8 @@ void TXshZeraryFxColumn::loadData(TIStream &is) { throw TException("expected "); is.closeChild(); } + } else if (loadCellMarks(tagName, is)) { + // do nothing } else throw TException("expected or "); is.closeChild(); @@ -186,6 +188,8 @@ void TXshZeraryFxColumn::saveData(TOStream &os) { } os.closeChild(); } + // cell marks + saveCellMarks(os); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzqt/menubarcommand.cpp b/toonz/sources/toonzqt/menubarcommand.cpp index 56f3ad53..ec2bf295 100644 --- a/toonz/sources/toonzqt/menubarcommand.cpp +++ b/toonz/sources/toonzqt/menubarcommand.cpp @@ -129,7 +129,8 @@ void CommandManager::define(CommandId id, CommandType type, (node->m_enabled && (node->m_handler || node->m_qaction->actionGroup() != 0)) || node->m_type == MiscCommandType || - node->m_type == ToolModifierCommandType); + node->m_type == ToolModifierCommandType || + node->m_type == CellMarkCommandType); m_qactionTable[qaction] = node; qaction->setShortcutContext(Qt::ApplicationShortcut); @@ -371,7 +372,7 @@ QAction *CommandManager::createAction(CommandId id, QObject *parent, if (!refAction) return 0; QString text = refAction->text(); if (node->m_onText != "" && node->m_offText != "") - text = state ? node->m_onText : node->m_offText; + text = state ? node->m_onText : node->m_offText; QAction *action = new QAction(text, parent); action->setShortcut(refAction->shortcut()); return action; @@ -533,24 +534,24 @@ void DVMenuAction::setActions(QList actions) { namespace { QString changeStringNumber(QString str, int index) { - QString newStr = str; - int n = 3; + QString newStr = str; + int n = 3; if (index >= 10) n = 4; QString number; newStr.replace(0, n, number.number(index + 1) + QString(". ")); return newStr; } -} +} // namespace //----------------------------------------------------------------------------- void DVMenuAction::onTriggered(QAction *action) { - QVariant data = action->data(); + QVariant data = action->data(); if (data.isValid()) m_triggeredActionIndex = data.toInt(); CommandManager::instance()->execute(action, menuAction()); int oldIndex = m_triggeredActionIndex; if (m_triggeredActionIndex != -1) m_triggeredActionIndex = -1; - QString str = data.toString(); + QString str = data.toString(); QAction *tableAction = CommandManager::instance()->getAction(str.toStdString().c_str()); if (tableAction || oldIndex == 0) return; diff --git a/toonz/sources/toonzqt/stageschematicnode.cpp b/toonz/sources/toonzqt/stageschematicnode.cpp index c790791c..1566e1e6 100644 --- a/toonz/sources/toonzqt/stageschematicnode.cpp +++ b/toonz/sources/toonzqt/stageschematicnode.cpp @@ -1020,8 +1020,8 @@ SplineAimChanger::~SplineAimChanger() {} void SplineAimChanger::mouseMoveEvent(QGraphicsSceneMouseEvent *me) { if (m_buttonState == Qt::LeftButton) { - bool increase = false; - int delta = me->screenPos().y() - me->lastScreenPos().y(); + bool increase = false; + int delta = me->screenPos().y() - me->lastScreenPos().y(); if (delta < 0) increase = true; m_delta += abs(delta); if (m_delta > 15) { @@ -2250,10 +2250,9 @@ StageSchematicGroupNode::StageSchematicGroupNode( , m_root(root) , m_groupedObj(groupedObj) { SchematicViewer *viewer = scene->getSchematicViewer(); - int i; - for (i = 0; i < m_groupedObj.size(); i++) m_groupedObj[i]->addRef(); - bool ret = true; + for (i = 0; i < m_groupedObj.size(); i++) m_groupedObj[i]->addRef(); + bool ret = true; std::wstring name = m_stageObject->getGroupName(false); m_name = QString::fromStdWString(name); From b21d463e35a8362aa1c6bbea0f16e129e7392a4b Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Thu, 9 Dec 2021 17:52:32 +0900 Subject: [PATCH 12/18] enable to cleanup with no line processing --- .../sources/include/toonz/cleanupparameters.h | 6 ++ toonz/sources/include/toonz/tcleanupper.h | 9 +-- toonz/sources/include/toonz/txshsimplelevel.h | 3 +- toonz/sources/tcleanupper/tcleanupper.cpp | 15 ++-- toonz/sources/toonz/cleanuppopup.cpp | 56 ++++++++++----- toonz/sources/toonz/cleanuppopup.h | 1 + toonz/sources/toonz/cleanupsettingsmodel.cpp | 5 +- toonz/sources/toonz/cleanupsettingspane.cpp | 63 +++++++++++----- toonz/sources/toonz/cleanupsettingspane.h | 2 + toonz/sources/toonzlib/cleanupparameters.cpp | 72 +++++++++++++------ toonz/sources/toonzlib/tcleanupper.cpp | 10 +-- toonz/sources/toonzlib/txshsimplelevel.cpp | 11 ++- 12 files changed, 176 insertions(+), 77 deletions(-) diff --git a/toonz/sources/include/toonz/cleanupparameters.h b/toonz/sources/include/toonz/cleanupparameters.h index e70062c5..897ee764 100644 --- a/toonz/sources/include/toonz/cleanupparameters.h +++ b/toonz/sources/include/toonz/cleanupparameters.h @@ -160,6 +160,12 @@ public: /*--- オフセットを軸ごとにロックする ---*/ bool m_offx_lock, m_offy_lock; + // hold brightness and contrast values for each line processing modes (grey + // and color). + double m_altBrightness, m_altContrast; + // exporting file format when Line Processing = None + std::string m_lpNoneFormat; + public: CleanupParameters(); CleanupParameters(const CleanupParameters &p) { assign(&p); } diff --git a/toonz/sources/include/toonz/tcleanupper.h b/toonz/sources/include/toonz/tcleanupper.h index 3a2ac95a..d8a1af86 100644 --- a/toonz/sources/include/toonz/tcleanupper.h +++ b/toonz/sources/include/toonz/tcleanupper.h @@ -89,10 +89,11 @@ time, to unlock a possibly useful memory block. */ CleanupPreprocessedImage *process(TRasterImageP &image, bool first_image, TRasterImageP &onlyResampledImage, - bool isCameraTest = false, - bool returnResampled = false, - bool onlyForSwatch = false, - TAffine *aff = 0); + bool isCameraTest = false, + bool returnResampled = false, + bool onlyForSwatch = false, + TAffine *aff = 0, + TRasterP templateForResampled = 0); void finalize(const TRaster32P &dst, CleanupPreprocessedImage *src); TToonzImageP finalize(CleanupPreprocessedImage *src, diff --git a/toonz/sources/include/toonz/txshsimplelevel.h b/toonz/sources/include/toonz/txshsimplelevel.h index 555099af..0a2c2b9f 100644 --- a/toonz/sources/include/toonz/txshsimplelevel.h +++ b/toonz/sources/include/toonz/txshsimplelevel.h @@ -202,7 +202,8 @@ table) it returns the proper insertion index // load icon (and image) data of all frames into cache void loadAllIconsAndPutInCache(bool cacheImagesAsWell); - TRasterImageP getFrameToCleanup(const TFrameId &fid) const; + TRasterImageP getFrameToCleanup(const TFrameId &fid, + bool toBeLineProcessed) const; std::string getImageId(const TFrameId &fid, int frameStatus = -1) const; std::string getIconId(const TFrameId &fid, int frameStatus = -1) const; diff --git a/toonz/sources/tcleanupper/tcleanupper.cpp b/toonz/sources/tcleanupper/tcleanupper.cpp index f5e10782..818b29aa 100644 --- a/toonz/sources/tcleanupper/tcleanupper.cpp +++ b/toonz/sources/tcleanupper/tcleanupper.cpp @@ -411,7 +411,10 @@ static void cleanupLevel(TXshSimpleLevel *xl, std::set fidsInXsheet, QString::fromStdString(fid.expand())); continue; } - TRasterImageP original = xl->getFrameToCleanup(fid); + CleanupParameters *params = scene->getProperties()->getCleanupParameters(); + // if lines are not processed, obtain the original sampled image + bool toBeLineProcessed = params->m_lineProcessingMode != lpNone; + TRasterImageP original = xl->getFrameToCleanup(fid, toBeLineProcessed); if (!original) { string err = " *error* missed frame"; m_userLog.error(err); @@ -419,11 +422,9 @@ static void cleanupLevel(TXshSimpleLevel *xl, std::set fidsInXsheet, continue; } - CleanupParameters *params = scene->getProperties()->getCleanupParameters(); - if (params->m_lineProcessingMode == lpNone) { - TRasterImageP ri; - if (params->m_autocenterType == CleanupTypes::AUTOCENTER_NONE) + TRasterImageP ri(original); + /*if (params->m_autocenterType == CleanupTypes::AUTOCENTER_NONE) ri = original; else { bool autocentered; @@ -432,7 +433,9 @@ static void cleanupLevel(TXshSimpleLevel *xl, std::set fidsInXsheet, m_userLog.error("The autocentering failed on the current drawing."); cout << "The autocentering failed on the current drawing." << endl; } - } + }*/ + cl->process(original, false, ri, false, true, true, nullptr, + ri->getRaster()); updater.update(fid, ri); continue; } diff --git a/toonz/sources/toonz/cleanuppopup.cpp b/toonz/sources/toonz/cleanuppopup.cpp index 653a63ba..4a90bfb9 100644 --- a/toonz/sources/toonz/cleanuppopup.cpp +++ b/toonz/sources/toonz/cleanuppopup.cpp @@ -565,6 +565,8 @@ bool CleanupPopup::analyzeCleanupList() { m_overwriteDialog->reset(); } + m_overwriteDialog->enableOptions(inputPath == outputPath); + // Prompt user for file conflict resolution clt.m_resolution = Resolution(m_overwriteDialog->execute(&clt.m_outputPath)); @@ -634,7 +636,7 @@ bool CleanupPopup::analyzeCleanupList() { TPointD outDpi; m_params->getOutputImageInfo(outRes, outDpi.x, outDpi.y); - if (oldRes != outRes) { + if (oldRes != outRes && inputPath != outputPath) { DVGui::warning( tr("The resulting resolution of level \"%1\"\ndoes not match " "with that of previously cleaned up level drawings.\n\nPlease " @@ -789,7 +791,13 @@ TImageP CleanupPopup::currentImage() const { if (!isValidPosition(m_idx)) return TImageP(); const CleanupLevel &cl = m_cleanupLevels[m_idx.first]; - return cl.m_sl->getFrameToCleanup(cl.m_frames[m_idx.second]); + + // if lines are not processed, obtain the original sampled image + bool toBeLineProcessed = + TCleanupper::instance()->getParameters()->m_lineProcessingMode != lpNone; + + return cl.m_sl->getFrameToCleanup(cl.m_frames[m_idx.second], + toBeLineProcessed); } //----------------------------------------------------------------------------- @@ -1195,19 +1203,35 @@ void CleanupPopup::cleanupFrame() { TCleanupper *cl = TCleanupper::instance(); const CleanupParameters *params = cl->getParameters(); + // Obtain the source dpi. Changed it to be done once at the first frame of + // each level in order to avoid the following problem: + // If the original raster level has no dpi (such as TGA images), obtaining + // dpi in every frame causes dpi mismatch between the first frame and the + // following frames, since the value + // TXshSimpleLevel::m_properties->getDpi() will be changed to the + // dpi of cleanup camera (= TLV's dpi) after finishing the first frame. + if (m_firstLevelFrame) { + TPointD dpi; + original->getDpi(dpi.x, dpi.y); + if (dpi.x == 0 && dpi.y == 0) dpi = sl->getProperties()->getDpi(); + cl->setSourceDpi(dpi); + } + if (params->m_lineProcessingMode == lpNone) { // No line processing TRasterImageP ri(original); - if (params->m_autocenterType != CleanupTypes::AUTOCENTER_NONE) { + /*if (params->m_autocenterType != CleanupTypes::AUTOCENTER_NONE) { bool autocentered; ri = cl->autocenterOnly(original, false, autocentered); if (!autocentered) DVGui::warning( QObject::tr("The autocentering failed on the current drawing.")); - } + }*/ + cl->process(original, false, ri, false, true, true, nullptr, + ri->getRaster()); - sl->setFrame(fid, ri); + if (TRaster32P(ri->getRaster())) sl->setFrame(fid, ri); // Update the associated file. In case the operation throws, oh well the // image gets skipped. @@ -1220,20 +1244,6 @@ void CleanupPopup::cleanupFrame() { } else { // Perform main processing - // Obtain the source dpi. Changed it to be done once at the first frame of - // each level in order to avoid the following problem: - // If the original raster level has no dpi (such as TGA images), obtaining - // dpi in every frame causes dpi mismatch between the first frame and the - // following frames, since the value - // TXshSimpleLevel::m_properties->getDpi() will be changed to the - // dpi of cleanup camera (= TLV's dpi) after finishing the first frame. - if (m_firstLevelFrame) { - TPointD dpi; - original->getDpi(dpi.x, dpi.y); - if (dpi.x == 0 && dpi.y == 0) dpi = sl->getProperties()->getDpi(); - cl->setSourceDpi(dpi); - } - CleanupPreprocessedImage *cpi; { TRasterImageP resampledRaster; @@ -1503,6 +1513,14 @@ void CleanupPopup::OverwriteDialog::reset() { //----------------------------------------------------------------------------- +void CleanupPopup::OverwriteDialog::enableOptions(bool writingOnSource) { + if (writingOnSource && m_buttonGroup->button(REPLACE)->isChecked()) + m_buttonGroup->button(OVERWRITE)->setChecked(true); + m_buttonGroup->button(REPLACE)->setDisabled(writingOnSource); +} + +//----------------------------------------------------------------------------- + QString CleanupPopup::OverwriteDialog::acceptResolution(void *obj, int resolution, bool applyToAll) { diff --git a/toonz/sources/toonz/cleanuppopup.h b/toonz/sources/toonz/cleanuppopup.h index 2698b0a3..89fd58a2 100644 --- a/toonz/sources/toonz/cleanuppopup.h +++ b/toonz/sources/toonz/cleanuppopup.h @@ -144,6 +144,7 @@ public: OverwriteDialog(); void reset() override; + void enableOptions(bool writingOnSource); private: DVGui::LineEdit *m_suffix; diff --git a/toonz/sources/toonz/cleanupsettingsmodel.cpp b/toonz/sources/toonz/cleanupsettingsmodel.cpp index 24752108..81a7382c 100644 --- a/toonz/sources/toonz/cleanupsettingsmodel.cpp +++ b/toonz/sources/toonz/cleanupsettingsmodel.cpp @@ -405,7 +405,7 @@ void CleanupSettingsModel::rebuildPreview() { void CleanupSettingsModel::processFrame(TXshSimpleLevel *sl, TFrameId fid) { assert(sl); - TRasterImageP imageToCleanup = sl->getFrameToCleanup(fid); + TRasterImageP imageToCleanup = sl->getFrameToCleanup(fid, true); if (!imageToCleanup) return; // Store the original image @@ -741,5 +741,6 @@ TFilePath CleanupSettingsModel::getOutputPath(TXshSimpleLevel *sl, const TFilePath &outDir = params->getPath(scene); return lineProcessing ? (outDir + inPath.getWideName()).withType("tlv") - : (outDir + inPath.getLevelNameW()).withType("tif"); + : (outDir + inPath.getLevelNameW()) + .withType(params->m_lpNoneFormat); } diff --git a/toonz/sources/toonz/cleanupsettingspane.cpp b/toonz/sources/toonz/cleanupsettingspane.cpp index 654c7b0c..50a82cd5 100644 --- a/toonz/sources/toonz/cleanupsettingspane.cpp +++ b/toonz/sources/toonz/cleanupsettingspane.cpp @@ -7,6 +7,7 @@ #include "toonz/tscenehandle.h" #include "toonz/toonzscene.h" #include "toonz/toonzfolders.h" +#include "toonz/cleanupcolorstyles.h" // ToonzQt includes #include "toonzqt/gutil.h" @@ -101,6 +102,8 @@ CleanupSettingsPane::CleanupSettingsPane(QWidget *parent) m_aaValueLabel = new QLabel(tr("MLAA Intensity:")); m_aaValue = new IntField(this); m_lineProcessing = new QComboBox(this); + m_lpNoneFormatLabel = new QLabel(tr("Format:")); + m_lpNoneFormat = new QComboBox(this); m_paletteViewer = new CleanupPaletteViewer(this); m_pathField = new CleanupSaveInField(this, QString("")); @@ -143,6 +146,13 @@ CleanupSettingsPane::CleanupSettingsPane(QWidget *parent) items << tr("None") << tr("Greyscale") << tr("Color"); m_lineProcessing->addItems(items); + items.clear(); + items << "tif" + << "png" + << "jpg" + << "tga"; + m_lpNoneFormat->addItems(items); + m_sharpness->setValues(90, 0, 100); m_despeckling->setValues(2, 0, 20); m_aaValue->setValues(70, 0, 100); @@ -223,15 +233,15 @@ CleanupSettingsPane::CleanupSettingsPane(QWidget *parent) lineProcLay->addWidget(m_aaValueLabel, 4, 0, Qt::AlignRight | Qt::AlignVCenter); lineProcLay->addWidget(m_aaValue, 4, 1); + lineProcLay->addWidget(m_lpNoneFormatLabel, 5, 0, + Qt::AlignRight | Qt::AlignVCenter); + lineProcLay->addWidget(m_lpNoneFormat, 5, 1, + Qt::AlignLeft | Qt::AlignVCenter); - lineProcLay->addWidget(m_paletteViewer, 5, 0, 1, 2); + lineProcLay->addWidget(m_paletteViewer, 6, 0, 1, 2); } - lineProcLay->setRowStretch(0, 0); - lineProcLay->setRowStretch(1, 0); - lineProcLay->setRowStretch(2, 0); - lineProcLay->setRowStretch(3, 0); - lineProcLay->setRowStretch(4, 0); - lineProcLay->setRowStretch(5, 1); + for (int r = 0; r <= 5; r++) lineProcLay->setRowStretch(r, 0); + lineProcLay->setRowStretch(6, 1); lineProcLay->setColumnStretch(0, 0); lineProcLay->setColumnStretch(1, 1); @@ -286,6 +296,8 @@ CleanupSettingsPane::CleanupSettingsPane(QWidget *parent) SLOT(onGenericSettingsChange())); ret = ret && connect(m_lineProcessing, SIGNAL(activated(int)), SLOT(onGenericSettingsChange())); + ret = ret && connect(m_lpNoneFormat, SIGNAL(activated(int)), + SLOT(onGenericSettingsChange())); ret = ret && connect(m_despeckling, SIGNAL(valueChanged(bool)), SLOT(onGenericSettingsChange())); ret = ret && connect(m_aaValue, SIGNAL(valueChanged(bool)), @@ -404,6 +416,8 @@ void CleanupSettingsPane::updateGui(CleanupParameters *params, m_sharpness->setValue(params->m_sharpness); m_despeckling->setValue(params->m_despeckling); m_aaValue->setValue(params->m_aaValue); + m_lpNoneFormat->setCurrentText( + QString::fromStdString(params->m_lpNoneFormat)); updateVisibility(); @@ -462,6 +476,8 @@ void CleanupSettingsPane::updateVisibility() { for (QWidget *w : m_lpWidgets) w->setVisible(lp); m_aaValueLabel->setVisible(MLAA); m_aaValue->setVisible(MLAA); + m_lpNoneFormatLabel->setVisible(!lp); + m_lpNoneFormat->setVisible(!lp); m_paletteViewer->setMode(lpGrey); m_paletteViewer->setContrastEnabled(m_antialias->currentIndex() == 0); @@ -525,15 +541,30 @@ void CleanupSettingsPane::onGenericSettingsChange() { params->m_flipy = m_flipY->isChecked(); //------ - - params->m_lineProcessingMode = m_lineProcessing->currentIndex(); - params->m_noAntialias = (m_antialias->currentIndex() > 0); - params->m_postAntialias = (m_antialias->currentIndex() == 2); - params->m_despeckling = m_despeckling->getValue(); - params->m_aaValue = m_aaValue->getValue(); - - if (params->m_lineProcessingMode == lpNone) - params->m_transparencyCheckEnabled = false; + if (params->m_lineProcessingMode != m_lineProcessing->currentIndex()) { + int oldMode = params->m_lineProcessingMode; + params->m_lineProcessingMode = m_lineProcessing->currentIndex(); + if (params->m_lineProcessingMode == lpNone) { + params->m_transparencyCheckEnabled = false; + } + // When switching from/to Greyscale processing, replace the brightness and + // contrast values by the registered ones. + if (oldMode == lpGrey || params->m_lineProcessingMode == lpGrey) { + TCleanupStyle *blackStyle = + dynamic_cast(params->m_cleanupPalette->getStyle(1)); + double b = params->m_altBrightness; + double c = params->m_altContrast; + params->m_altBrightness = blackStyle->getBrightness(); + params->m_altContrast = blackStyle->getContrast(); + blackStyle->setBrightness(b); + blackStyle->setContrast(c); + } + } + params->m_noAntialias = (m_antialias->currentIndex() > 0); + params->m_postAntialias = (m_antialias->currentIndex() == 2); + params->m_despeckling = m_despeckling->getValue(); + params->m_aaValue = m_aaValue->getValue(); + params->m_lpNoneFormat = m_lpNoneFormat->currentText().toStdString(); //------ m_cameraWidget->getFields(model->getCurrentParameters()); diff --git a/toonz/sources/toonz/cleanupsettingspane.h b/toonz/sources/toonz/cleanupsettingspane.h index d27e60e0..7d8a10f1 100644 --- a/toonz/sources/toonz/cleanupsettingspane.h +++ b/toonz/sources/toonz/cleanupsettingspane.h @@ -62,6 +62,8 @@ private: QLabel *m_aaValueLabel; DVGui::IntField *m_aaValue; QComboBox *m_lineProcessing; + QLabel *m_lpNoneFormatLabel; + QComboBox *m_lpNoneFormat; //----Cleanup Palette CleanupPaletteViewer *m_paletteViewer; //----Bottom Parts diff --git a/toonz/sources/toonzlib/cleanupparameters.cpp b/toonz/sources/toonzlib/cleanupparameters.cpp index b5bf9e7d..ca83821b 100644 --- a/toonz/sources/toonzlib/cleanupparameters.cpp +++ b/toonz/sources/toonzlib/cleanupparameters.cpp @@ -6,6 +6,7 @@ #include "toonz/toonzscene.h" #include "toonz/sceneproperties.h" #include "toonz/txshleveltypes.h" +#include "toonz/cleanupcolorstyles.h" #include "tsystem.h" #include "tenv.h" #include "tconvert.h" @@ -193,7 +194,10 @@ CleanupParameters::CleanupParameters() , m_dirtyFlag(false) //, m_resName("") , m_offx_lock(false) - , m_offy_lock(false) {} + , m_offy_lock(false) + , m_altBrightness(0) + , m_altContrast(50) + , m_lpNoneFormat("tif") {} //--------------------------------------------------------- @@ -300,8 +304,11 @@ void CleanupParameters::assign(const CleanupParameters *param, if (clonePalette && param->m_cleanupPalette) m_cleanupPalette = param->m_cleanupPalette->clone(); - m_offx_lock = param->m_offx_lock; - m_offy_lock = param->m_offy_lock; + m_offx_lock = param->m_offx_lock; + m_offy_lock = param->m_offy_lock; + m_altBrightness = param->m_altBrightness; + m_altContrast = param->m_altContrast; + m_lpNoneFormat = param->m_lpNoneFormat; } //--------------------------------------------------------- @@ -330,10 +337,10 @@ void CleanupParameters::saveData(TOStream &os) const { attr.clear(); std::string flip = std::string(m_flipx ? "x" : "") + std::string(m_flipy ? "y" : ""); - if (flip != "") attr["flip"] = flip; + if (flip != "") attr["flip"] = flip; if (m_rotate != 0) attr["rotate"] = std::to_string(m_rotate); - if (m_offx != 0.0) attr["xoff"] = std::to_string(m_offx); - if (m_offy != 0.0) attr["yoff"] = std::to_string(m_offy); + if (m_offx != 0.0) attr["xoff"] = std::to_string(m_offx); + if (m_offy != 0.0) attr["yoff"] = std::to_string(m_offy); os.openCloseChild("transform", attr); } @@ -366,6 +373,8 @@ void CleanupParameters::saveData(TOStream &os) const { os.openCloseChild("fdg", attr); attr.clear(); if (m_path != TFilePath()) os.child("path") << m_path; + os.child("altBrightnessContrast") << m_altBrightness << m_altContrast; + os.child("lpNoneFormat") << m_lpNoneFormat; } //--------------------------------------------------------- @@ -380,6 +389,9 @@ void CleanupParameters::loadData(TIStream &is, bool globalParams) { m_lineProcessingMode = lpNone; m_noAntialias = false; m_postAntialias = false; + // hold brightness and contrast values of another processing mode + m_altBrightness = -1.0; + m_altContrast = -1.0; while (is.matchTag(tagName)) { if (tagName == "cleanupPalette") { @@ -390,42 +402,42 @@ void CleanupParameters::loadData(TIStream &is, bool globalParams) { m_camera.loadData(is); is.closeChild(); } else if (tagName == "autoCenter") { - m_autocenterType = AUTOCENTER_FDG; - std::string s = is.getTagAttribute("type"); + m_autocenterType = AUTOCENTER_FDG; + std::string s = is.getTagAttribute("type"); if (s != "" && isInt(s)) m_autocenterType = (AUTOCENTER_TYPE)std::stoi(s); - s = is.getTagAttribute("pegHoles"); + s = is.getTagAttribute("pegHoles"); if (s != "" && isInt(s)) m_pegSide = (PEGS_SIDE)std::stoi(s); } else if (tagName == "transform") { - std::string s = is.getTagAttribute("flip"); - m_flipx = (s.find("x") != std::string::npos); - m_flipy = (s.find("y") != std::string::npos); - s = is.getTagAttribute("rotate"); - if (s != "" && isInt(s)) m_rotate = std::stoi(s); - s = is.getTagAttribute("xoff"); + std::string s = is.getTagAttribute("flip"); + m_flipx = (s.find("x") != std::string::npos); + m_flipy = (s.find("y") != std::string::npos); + s = is.getTagAttribute("rotate"); + if (s != "" && isInt(s)) m_rotate = std::stoi(s); + s = is.getTagAttribute("xoff"); if (s != "" && isDouble(s)) m_offx = std::stod(s); - s = is.getTagAttribute("yoff"); + s = is.getTagAttribute("yoff"); if (s != "" && isDouble(s)) m_offy = std::stod(s); } else if (tagName == "lineProcessing") { - m_lineProcessingMode = lpGrey; - std::string s = is.getTagAttribute("sharpness"); + m_lineProcessingMode = lpGrey; + std::string s = is.getTagAttribute("sharpness"); if (s != "" && isDouble(s)) m_sharpness = std::stod(s); s = is.getTagAttribute("autoAdjust"); if (s != "" && isDouble(s)) m_autoAdjustMode = (CleanupTypes::AUTO_ADJ_MODE)std::stoi(s); - s = is.getTagAttribute("mode"); + s = is.getTagAttribute("mode"); if (s != "" && s == "color") m_lineProcessingMode = lpColor; } else if (tagName == "despeckling") { - std::string s = is.getTagAttribute("value"); + std::string s = is.getTagAttribute("value"); if (s != "" && isInt(s)) m_despeckling = std::stoi(s); } else if (tagName == "aaValue") { - std::string s = is.getTagAttribute("value"); + std::string s = is.getTagAttribute("value"); if (s != "" && isInt(s)) m_aaValue = std::stoi(s); } else if (tagName == "noAntialias") m_noAntialias = true; else if (tagName == "MLAA") m_postAntialias = true; else if (tagName == "closestField") { - std::string s = is.getTagAttribute("value"); + std::string s = is.getTagAttribute("value"); if (s != "" && isDouble(s)) m_closestField = std::stod(s); } else if (tagName == "fdg") { std::string s = is.getTagAttribute("name"); @@ -433,10 +445,26 @@ void CleanupParameters::loadData(TIStream &is, bool globalParams) { } else if (tagName == "path") { is >> m_path; is.closeChild(); + } else if (tagName == "altBrightnessContrast") { + is >> m_altBrightness >> m_altContrast; + is.closeChild(); + } else if (tagName == "lpNoneFormat") { + is >> m_lpNoneFormat; + is.closeChild(); } else is.skipCurrentTag(); } + if ((m_altBrightness < 0.0 || m_altContrast < 0.0) && m_cleanupPalette && + m_cleanupPalette->getStyleCount() >= 2) { + TCleanupStyle *blackStyle = + dynamic_cast(m_cleanupPalette->getStyle(1)); + if (blackStyle) { + m_altBrightness = blackStyle->getBrightness(); + m_altContrast = blackStyle->getContrast(); + } + } + CleanupParameters::LastSavedParameters.assign(this); if (globalParams) CleanupParameters::GlobalParameters.assign(this); } diff --git a/toonz/sources/toonzlib/tcleanupper.cpp b/toonz/sources/toonzlib/tcleanupper.cpp index 3c69ba8b..3b66ef97 100644 --- a/toonz/sources/toonzlib/tcleanupper.cpp +++ b/toonz/sources/toonzlib/tcleanupper.cpp @@ -160,7 +160,7 @@ HSVColor HSVColor::fromRGB(double r, double g, double b) { h = 2.0 + (b - r) / delta; else if (b == max) h = 4.0 + (r - g) / delta; - h = h * 60.0; + h = h * 60.0; if (h < 0) h += 360.0; } @@ -569,7 +569,7 @@ TRasterP TCleanupper::processColors(const TRasterP &rin) { CleanupPreprocessedImage *TCleanupper::process( TRasterImageP &image, bool first_image, TRasterImageP &onlyResampledImage, bool isCameraTest, bool returnResampled, bool onlyForSwatch, - TAffine *resampleAff) { + TAffine *resampleAff, TRasterP templateForResampled) { TAffine aff; double blur; TDimension outDim(0, 0); @@ -682,7 +682,9 @@ CleanupPreprocessedImage *TCleanupper::process( TRasterP tmp_ras; if (returnResampled || (fromGr8 && toGr8)) { - if (fromGr8 && toGr8) + if (templateForResampled) + tmp_ras = templateForResampled->create(outDim.lx, outDim.ly); + else if (fromGr8 && toGr8) tmp_ras = TRasterGR8P(outDim); else tmp_ras = TRaster32P(outDim); @@ -709,7 +711,7 @@ CleanupPreprocessedImage *TCleanupper::process( flt_type = TRop::Hann2; TRop::resample(tmp_ras, image->getRaster(), aff, flt_type, blur); - if ((TRaster32P)tmp_ras) + if ((TRaster32P)tmp_ras && !templateForResampled) // Add white background to deal with semitransparent pixels TRop::addBackground(tmp_ras, TPixel32::White); diff --git a/toonz/sources/toonzlib/txshsimplelevel.cpp b/toonz/sources/toonzlib/txshsimplelevel.cpp index 2b77d696..b4d5b067 100644 --- a/toonz/sources/toonzlib/txshsimplelevel.cpp +++ b/toonz/sources/toonzlib/txshsimplelevel.cpp @@ -643,7 +643,8 @@ void TXshSimpleLevel::loadAllIconsAndPutInCache(bool cacheImagesAsWell) { //----------------------------------------------------------------------------- -TRasterImageP TXshSimpleLevel::getFrameToCleanup(const TFrameId &fid) const { +TRasterImageP TXshSimpleLevel::getFrameToCleanup(const TFrameId &fid, + bool toBeLineProcessed) const { assert(m_type != UNKNOWN_XSHLEVEL); FramesSet::const_iterator ft = m_frames.find(fid); @@ -653,8 +654,12 @@ TRasterImageP TXshSimpleLevel::getFrameToCleanup(const TFrameId &fid) const { std::string imageId = getImageId(fid, flag ? Scanned : 0); ImageLoader::BuildExtData extData(this, fid, 1); - TRasterImageP img = ImageManager::instance()->getImage( - imageId, ImageManager::dontPutInCache, &extData); + + UCHAR imFlags = ImageManager::dontPutInCache; + // if lines are not processed, obtain the original sampled image + if (!toBeLineProcessed) imFlags |= ImageManager::is64bitEnabled; + TRasterImageP img = + ImageManager::instance()->getImage(imageId, imFlags, &extData); if (!img) return img; double x_dpi, y_dpi; From 8230920a132f89d9ca46c02d74fd02bad2d960b1 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Fri, 10 Dec 2021 17:07:39 +0900 Subject: [PATCH 13/18] view palette file --- toonz/sources/toonz/filebrowser.cpp | 4 +++- toonz/sources/toonz/fileselection.cpp | 19 +++++++++++++++++-- toonz/sources/toonz/tpanels.cpp | 2 ++ toonz/sources/toonz/tpanels.h | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/toonz/sources/toonz/filebrowser.cpp b/toonz/sources/toonz/filebrowser.cpp index de37b63a..0f1f573d 100644 --- a/toonz/sources/toonz/filebrowser.cpp +++ b/toonz/sources/toonz/filebrowser.cpp @@ -1193,7 +1193,9 @@ QMenu *FileBrowser::getContextMenu(QWidget *parent, int index) { } for (i = 0; i < files.size(); i++) - if (!TFileType::isViewable(TFileType::getInfo(files[i]))) break; + if (!TFileType::isViewable(TFileType::getInfo(files[i])) && + files[i].getType() != "tpl") + break; if (i == files.size()) { std::string type = files[0].getType(); for (j = 0; j < files.size(); j++) diff --git a/toonz/sources/toonz/fileselection.cpp b/toonz/sources/toonz/fileselection.cpp index 559cd1d3..c022d28c 100644 --- a/toonz/sources/toonz/fileselection.cpp +++ b/toonz/sources/toonz/fileselection.cpp @@ -23,6 +23,7 @@ #include "toonzqt/icongenerator.h" #include "toonzqt/gutil.h" #include "historytypes.h" +#include "toonzqt/menubarcommand.h" // TnzLib includes #include "toonz/tproject.h" @@ -30,6 +31,9 @@ #include "toonz/sceneresources.h" #include "toonz/preferences.h" #include "toonz/tscenehandle.h" +#include "toonz/studiopalette.h" +#include "toonz/palettecontroller.h" +#include "toonz/tpalettehandle.h" // TnzCore includes #include "tfiletype.h" @@ -185,6 +189,8 @@ public: return str; } }; + +TPaletteP viewedPalette; //----------------------------------------------------------------------------- } // namespace @@ -372,12 +378,21 @@ void FileSelection::viewFile() { getSelectedFiles(files); int i = 0; for (i = 0; i < files.size(); i++) { - if (!TFileType::isViewable(TFileType::getInfo(files[0]))) continue; + if (!TFileType::isViewable(TFileType::getInfo(files[i])) && + files[i].getType() != "tpl") + continue; if (Preferences::instance()->isDefaultViewerEnabled() && (files[i].getType() == "avi")) QDesktopServices::openUrl(QUrl("file:///" + toQString(files[i]))); - else { + else if (files[i].getType() == "tpl") { + viewedPalette = StudioPalette::instance()->getPalette(files[i], false); + TApp::instance() + ->getPaletteController() + ->getCurrentLevelPalette() + ->setPalette(viewedPalette.getPointer()); + CommandManager::instance()->execute("MI_OpenPalette"); + } else { FlipBook *fb = ::viewFile(files[i]); if (fb) { FileBrowserPopup::setModalBrowserToParent(fb->parentWidget()); diff --git a/toonz/sources/toonz/tpanels.cpp b/toonz/sources/toonz/tpanels.cpp index c30b845e..24de316b 100644 --- a/toonz/sources/toonz/tpanels.cpp +++ b/toonz/sources/toonz/tpanels.cpp @@ -574,6 +574,7 @@ void PaletteViewerPanel::onFreezeButtonToggled(bool frozen) { // Cambio il livello corrente if (!frozen) { + m_frozenPalette = nullptr; std::set levels; TXsheet *xsheet = app->getCurrentXsheet()->getXsheet(); int row, column; @@ -605,6 +606,7 @@ void PaletteViewerPanel::onFreezeButtonToggled(bool frozen) { app->getCurrentLevel()->setLevel(level); m_paletteViewer->setPaletteHandle(ph); } else { + m_frozenPalette = ph->getPalette(); m_paletteHandle->setPalette(ph->getPalette()); m_paletteViewer->setPaletteHandle(m_paletteHandle); } diff --git a/toonz/sources/toonz/tpanels.h b/toonz/sources/toonz/tpanels.h index a42628ee..f293a0fb 100644 --- a/toonz/sources/toonz/tpanels.h +++ b/toonz/sources/toonz/tpanels.h @@ -44,6 +44,7 @@ class PaletteViewerPanel final : public StyleShortcutSwitchablePanel { PaletteViewer *m_paletteViewer; bool m_isFrozen; + TPaletteP m_frozenPalette; public: PaletteViewerPanel(QWidget *parent); From 388520b4c12096f82045e10d294b96a7778a006f Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 13 Dec 2021 09:22:20 +0900 Subject: [PATCH 14/18] fix tvp json export --- toonz/sources/toonz/tvpjson_io.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toonz/sources/toonz/tvpjson_io.cpp b/toonz/sources/toonz/tvpjson_io.cpp index 1078bfa7..b50e24e9 100644 --- a/toonz/sources/toonz/tvpjson_io.cpp +++ b/toonz/sources/toonz/tvpjson_io.cpp @@ -366,7 +366,7 @@ void TvpJsonClip::build(ToonzScene* scene, TXsheet* xsheet) { continue; } TvpJsonLayer layer; - layer.build(col, scene, column); + layer.build(m_layers.size(), scene, column); if (!layer.isEmpty()) m_layers.append(layer); } } From df4c906f6c55d19bcef30b9e4dc655784b199731 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 13 Dec 2021 12:00:39 +0900 Subject: [PATCH 15/18] fix frameIds inconsistency in level strip commands --- toonz/sources/toonz/filmstripcommand.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/toonz/sources/toonz/filmstripcommand.cpp b/toonz/sources/toonz/filmstripcommand.cpp index 4e983ada..1b4d9851 100644 --- a/toonz/sources/toonz/filmstripcommand.cpp +++ b/toonz/sources/toonz/filmstripcommand.cpp @@ -51,7 +51,8 @@ //============================================================================= TFrameId operator+(const TFrameId &fid, int d) { - return TFrameId(fid.getNumber() + d, fid.getLetter()); + return TFrameId(fid.getNumber() + d, fid.getLetter(), fid.getZeroPadding(), + fid.getStartSeqInd()); } //----------------------------------------------------------------------------- @@ -1367,10 +1368,14 @@ void FilmstripCmd::addFrames(TXshSimpleLevel *sl, int start, int end, std::vector oldFids; sl->getFids(oldFids); + TFrameId tmplFid; + if (!oldFids.empty()) tmplFid = oldFids.front(); + std::set fidsToInsert; int frame = 0; for (frame = start; frame <= end; frame += step) - fidsToInsert.insert(TFrameId(frame)); + fidsToInsert.insert(TFrameId(frame, "", tmplFid.getZeroPadding(), + tmplFid.getStartSeqInd())); makeSpaceForFids(sl, fidsToInsert); @@ -1499,7 +1504,8 @@ void FilmstripCmd::renumber( if (tmp.count(tarFid) > 0) { do { tarFid = - TFrameId(tarFid.getNumber(), getNextLetter(tarFid.getLetter())); + TFrameId(tarFid.getNumber(), getNextLetter(tarFid.getLetter()), + tarFid.getZeroPadding(), tarFid.getStartSeqInd()); } while (!tarFid.getLetter().isEmpty() && tmp.count(tarFid) > 0); if (tarFid.getLetter().isEmpty()) { // todo: error message @@ -1557,7 +1563,8 @@ void FilmstripCmd::renumber(TXshSimpleLevel *sl, std::set &frames, std::vector::iterator j = fids.begin(); for (it = frames.begin(); it != frames.end(); ++it) { TFrameId srcFid(*it); - TFrameId dstFid(frame); + TFrameId dstFid(frame, "", srcFid.getZeroPadding(), + srcFid.getStartSeqInd()); frame += stepFrame; // faccio il controllo su tmp e non su fids. considera: // fids = [1,2,3,4], renumber = [2->3,3->5] @@ -1590,7 +1597,8 @@ void FilmstripCmd::renumber(TXshSimpleLevel *sl, std::set &frames, it2 = frames.begin(); std::set newFrames; for (i = 0; i < frames.size(); i++, it2++) - newFrames.insert(TFrameId(startFrame + (i * stepFrame), it2->getLetter())); + newFrames.insert(TFrameId(startFrame + (i * stepFrame), it2->getLetter(), + it2->getZeroPadding(), it2->getStartSeqInd())); assert(frames.size() == newFrames.size()); frames.swap(newFrames); From 68186e80a5417eb4dcb863aa3c4d7b8b8b8c8e84 Mon Sep 17 00:00:00 2001 From: Rozhuk Ivan Date: Tue, 14 Dec 2021 00:30:16 +0300 Subject: [PATCH 16/18] Fix build with clang 13: no member named 'copy' in namespace 'std' --- toonz/sources/include/tcg/poly_ops.h | 1 + 1 file changed, 1 insertion(+) diff --git a/toonz/sources/include/tcg/poly_ops.h b/toonz/sources/include/tcg/poly_ops.h index 1825837a..741b91a1 100644 --- a/toonz/sources/include/tcg/poly_ops.h +++ b/toonz/sources/include/tcg/poly_ops.h @@ -5,6 +5,7 @@ // tcg includes #include "macros.h" +#include /*! \file tcg_poly_ops.h From 69a6f2a55b51c008f4547623ab638e532be03392 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Mon, 13 Dec 2021 15:44:45 +0900 Subject: [PATCH 17/18] Note column enhancement --- toonz/sources/toonz/exportxsheetpdf.cpp | 198 +++++++- toonz/sources/toonz/exportxsheetpdf.h | 13 +- toonz/sources/toonz/xshcellviewer.cpp | 454 ++++++++++++++---- toonz/sources/toonz/xshcellviewer.h | 1 + .../sources/toonzlib/txshsoundtextcolumn.cpp | 55 ++- 5 files changed, 622 insertions(+), 99 deletions(-) diff --git a/toonz/sources/toonz/exportxsheetpdf.cpp b/toonz/sources/toonz/exportxsheetpdf.cpp index d1425af8..59bd044b 100644 --- a/toonz/sources/toonz/exportxsheetpdf.cpp +++ b/toonz/sources/toonz/exportxsheetpdf.cpp @@ -23,6 +23,8 @@ #include "toonz/tstageobject.h" #include "toonz/preferences.h" #include "toonz/toonzfolders.h" +#include "toonz/txshsoundtextcolumn.h" +#include "toonz/txshsoundtextlevel.h" // TnzCore includes #include "tsystem.h" @@ -74,6 +76,8 @@ TEnv::IntVar XShPdfExportPrintSoundtrack("XShPdfExportPrintSoundtrack", 0); TEnv::IntVar XShPdfExportSerialFrameNumber("XShPdfExportSerialFrameNumber", 0); // print level name on the bottom TEnv::IntVar XShPdfExportLevelNameOnBottom("XShPdfExportLevelNameOnBottom", 0); +// print dialogue +TEnv::IntVar XShPdfExportPrintDialogue("XShPdfExportPrintDialogue", 0); // print scene name TEnv::IntVar XShPdfExportPrintSceneName("XShPdfExportPrintSceneName", 0); // template font @@ -738,7 +742,7 @@ void XSheetPDFTemplate::drawDialogBlock(QPainter& painter, const int framePage, painter.restore(); // register sound cells - if (m_info.drawSound) + if (m_info.drawSound || m_noteColumn) registerSoundRects(painter, param(DialogColWidth), bodyId); } painter.restore(); @@ -1136,6 +1140,153 @@ void XSheetPDFTemplate::drawSound(QPainter& painter, int framePage) { } } +void XSheetPDFTemplate::drawDialogue(QPainter& painter, int framePage) { + if (!m_noteColumn || m_soundCellRects.isEmpty()) return; + + QFont font = painter.font(); + font.setPixelSize(m_soundCellRects[0].height()); + painter.setFont(font); + + QFont smallFont(font); + smallFont.setPixelSize(font.pixelSize() * 2 / 3); + + QFont largeFont(font); + largeFont.setPixelSize(font.pixelSize() * 13 / 10); + int heightThres = QFontMetrics(largeFont).height(); + + int r0, r1; + m_noteColumn->getRange(r0, r1); + + // obtain frame range to be printed in the current page + int printFrameR0 = framePage * param(FrameLength); + int printFrameR1 = printFrameR0 + param(FrameLength) - 1; + + // compute for each body + int framesPerBody = 72; + int bodyFrameR0 = printFrameR0; + int bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + while (bodyFrameR1 <= printFrameR1) { + // move to next body if the current body is out of range + if (r1 < bodyFrameR0 || bodyFrameR1 < r0) { + // to next body + bodyFrameR0 = bodyFrameR1 + 1; + bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + continue; + } + + // frame range to be printed + int drawStart = std::max(r0, bodyFrameR0); + int drawEnd = std::min(r1, std::min(bodyFrameR1, printFrameR1)); + + int rStart = drawStart; + int rEnd = drawEnd; + // obtain top row of the fist note block + if (!m_noteColumn->getCell(drawStart).isEmpty()) { + while (rStart > 0 && m_noteColumn->getCell(rStart - 1) == + m_noteColumn->getCell(drawStart)) { + rStart--; + } + } + // obtain bottom row of the last note block + if (!m_noteColumn->getCell(drawEnd).isEmpty()) { + while (m_noteColumn->getCell(rEnd + 1) == + m_noteColumn->getCell(drawEnd)) { + rEnd++; + } + } + + for (int row = rStart; row <= drawEnd; row++) { + TXshCell cell = m_noteColumn->getCell(row); + if (cell.isEmpty()) continue; + + // check how long the same content continues + int rowTo = row; + while (m_noteColumn->getCell(rowTo + 1) == cell) { + rowTo++; + } + int blockLength = rowTo - row + 1; + + QString text = cell.getSoundTextLevel()->getFrameText( + cell.m_frameId.getNumber() - 1); + int textCount = text.count(); + // separate text if it overflows the body + if (row < drawStart) { + int partialBlockLength = rowTo - drawStart + 1; + int partialTextCount = (int)std::round( + (double)(textCount * partialBlockLength) / (double)blockLength); + text = text.mid(textCount - partialTextCount); + textCount = partialTextCount; + row = drawStart; + blockLength = partialBlockLength; + } + // draw start mark + else { + int topRectId = row - printFrameR0; + QRect rect = m_soundCellRects.at(topRectId); + painter.drawLine(rect.topLeft(), rect.topRight()); + } + + if (rowTo > drawEnd) { + int partialBlockLength = drawEnd - row + 1; + int partialTextCount = (int)std::round( + (double)(textCount * partialBlockLength) / (double)blockLength); + text = text.mid(0, partialTextCount); + textCount = partialTextCount; + rowTo = drawEnd; + } + // draw end mark + else if (m_noteColumn->getCell(rowTo + 1).isEmpty()) { + int bottomRectId = rowTo - printFrameR0; + QRect rect = m_soundCellRects.at(bottomRectId); + drawEndMark(painter, rect); + } + if (text.isEmpty()) { + row = rowTo; + continue; + } + + int normalLettersPerChunk = textCount * m_soundCellRects[0].width() / + QFontMetrics(font).boundingRect(text).width(); + int maxLettersPerChunk = + textCount * m_soundCellRects[0].width() / + QFontMetrics(smallFont).boundingRect(text).width(); + + int lettersPerChunk = + (int)std::ceil((double)textCount / ((double)blockLength)); + lettersPerChunk = std::min(lettersPerChunk, maxLettersPerChunk); + int chunkCount = + (int)std::ceil((double)textCount / (double)(lettersPerChunk)); + chunkCount = std::min(chunkCount, (int)((double)(blockLength)*1.5)); + + int topRectId = row - printFrameR0; + int bottomRectId = rowTo - printFrameR0; + // unite the cell rects and divide by the amount of chunks + QRect unitedRect = m_soundCellRects.at(topRectId).united( + m_soundCellRects.at(bottomRectId)); + // check if the large font is available + if (lettersPerChunk == 1 && unitedRect.height() / textCount > heightThres) + painter.setFont(largeFont); + else if (lettersPerChunk > normalLettersPerChunk) + painter.setFont(smallFont); + else + painter.setFont(font); + // draw text + for (int c = 0; c < chunkCount; c++) { + int y0 = unitedRect.top() + unitedRect.height() * c / chunkCount; + int y1 = unitedRect.top() + unitedRect.height() * (c + 1) / chunkCount; + QRect tmpRect(unitedRect.left(), y0, unitedRect.width(), y1 - y0 + 1); + painter.drawText(tmpRect, Qt::AlignCenter, + text.mid(c * lettersPerChunk, lettersPerChunk)); + } + + row = rowTo; + } + // to next body + bodyFrameR0 = bodyFrameR1 + 1; + bodyFrameR1 = bodyFrameR0 + framesPerBody - 1; + } +} + XSheetPDFTemplate::XSheetPDFTemplate( const QList>& columns, const int duration) : m_columns(columns), m_duration(duration), m_useExtraColumns(false) {} @@ -1213,6 +1364,10 @@ void XSheetPDFTemplate::drawXsheetContents(QPainter& painter, int framePage, painter.setPen( QPen(Qt::black, mm2px(0.5), Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.setFont(m_info.contentsFontFamily); + + // draw dialogue + drawDialogue(painter, framePage); + int colsInPage = columnsInPage(); int startColId = colsInPage * parallelPage; int startFrame = param(FrameLength) * framePage; @@ -1723,8 +1878,10 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() new QCheckBox(tr("Put Serial Frame Numbers Over Pages"), this); m_levelNameOnBottomCB = new QCheckBox(tr("Print Level Names On The Bottom"), this); - m_sceneNameEdit = new QLineEdit(this); - m_memoEdit = new QTextEdit(this); + m_drawDialogueCB = new QCheckBox(tr("Print Dialogue"), this); + m_dialogueColCombo = new QComboBox(); + m_sceneNameEdit = new QLineEdit(this); + m_memoEdit = new QTextEdit(this); m_logoTxtRB = new QRadioButton(tr("Text"), this); m_logoImgRB = new QRadioButton(tr("Image"), this); @@ -1766,6 +1923,7 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() m_memoEdit->setStyleSheet( "background:white;\ncolor:black;\nborder:1 solid black;"); m_memoEdit->setFixedHeight(150); + m_dialogueColCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); m_sceneNameEdit->setFixedWidth(100); m_continuousLineCombo->addItem(tr("Always"), Line_Always); @@ -1888,7 +2046,10 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() checksLay->addWidget(m_addSceneNameCB, 1, 1); checksLay->addWidget(m_sceneNameEdit, 1, 2, Qt::AlignLeft | Qt::AlignVCenter); - checksLay->addWidget(m_levelNameOnBottomCB, 2, 0, 1, 3); + checksLay->addWidget(m_levelNameOnBottomCB, 2, 0); + checksLay->addWidget(m_drawDialogueCB, 2, 1); + checksLay->addWidget(m_dialogueColCombo, 2, 2, + Qt::AlignLeft | Qt::AlignVCenter); } checksLay->setColumnStretch(0, 2); checksLay->setColumnStretch(1, 1); @@ -1980,6 +2141,11 @@ ExportXsheetPdfPopup::ExportXsheetPdfPopup() SLOT(updatePreview())); connect(m_levelNameOnBottomCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_drawDialogueCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_drawDialogueCB, SIGNAL(clicked(bool)), m_dialogueColCombo, + SLOT(setEnabled(bool))); + connect(m_dialogueColCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); connect(m_addSceneNameCB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); connect(m_addSceneNameCB, SIGNAL(clicked(bool)), m_sceneNameEdit, SLOT(setEnabled(bool))); @@ -2091,6 +2257,7 @@ void ExportXsheetPdfPopup::initialize() { m_columns.clear(); m_soundColumns.clear(); + m_noteColumns.clear(); for (int col = 0; col < xsheet->getColumnCount(); col++) { if (xsheet->isColumnEmpty(col)) continue; @@ -2100,6 +2267,13 @@ void ExportXsheetPdfPopup::initialize() { continue; } + TXshSoundTextColumn* noteColumn = + xsheet->getColumn(col)->getSoundTextColumn(); + if (noteColumn) { + m_noteColumns.insert(col, noteColumn); + continue; + } + TXshLevelColumn* column = xsheet->getColumn(col)->getLevelColumn(); if (!column) continue; // do not export if the "eye" (render) button is off @@ -2123,6 +2297,12 @@ void ExportXsheetPdfPopup::initialize() { m_sceneNameEdit->setText( (scene->isUntitled()) ? "" : QString::fromStdWString(scene->getSceneName())); + m_drawDialogueCB->setDisabled(m_noteColumns.isEmpty()); + m_dialogueColCombo->setEnabled(!m_noteColumns.isEmpty() && + m_drawDialogueCB->isChecked()); + m_dialogueColCombo->clear(); + for (auto colId : m_noteColumns.keys()) + m_dialogueColCombo->addItem(tr("Col%1").arg(colId + 1), colId); initTemplate(); @@ -2146,6 +2326,7 @@ void ExportXsheetPdfPopup::saveSettings() { XShPdfExportPrintSceneName = (m_addSceneNameCB->isChecked()) ? 1 : 0; XShPdfExportSerialFrameNumber = (m_serialFrameNumberCB->isChecked()) ? 1 : 0; XShPdfExportLevelNameOnBottom = (m_levelNameOnBottomCB->isChecked()) ? 1 : 0; + XShPdfExportPrintDialogue = (m_drawDialogueCB->isChecked()) ? 1 : 0; XShPdfExportTemplateFont = m_templateFontCB->currentFont().family().toStdString(); XShPdfExportOutputFont = @@ -2182,6 +2363,7 @@ void ExportXsheetPdfPopup::loadSettings() { m_addSceneNameCB->setChecked(XShPdfExportPrintSceneName != 0); m_serialFrameNumberCB->setChecked(XShPdfExportSerialFrameNumber != 0); m_levelNameOnBottomCB->setChecked(XShPdfExportLevelNameOnBottom != 0); + m_drawDialogueCB->setChecked(XShPdfExportPrintDialogue != 0); QString tmplFont = QString::fromStdString(XShPdfExportTemplateFont); if (!tmplFont.isEmpty()) m_templateFontCB->setCurrentFont(QFont(tmplFont)); @@ -2204,6 +2386,8 @@ void ExportXsheetPdfPopup::loadSettings() { m_logoTextEdit->setEnabled(m_logoTxtRB->isChecked()); m_logoImgPathField->setEnabled(m_logoImgRB->isChecked()); m_sceneNameEdit->setEnabled(m_addSceneNameCB->isChecked()); + m_dialogueColCombo->setEnabled(m_drawDialogueCB->isChecked() && + m_drawDialogueCB->isEnabled()); int id = XShPdfExportTick1Id; m_tick1IdCombo->setCurrentIndex(m_tick1IdCombo->findData(id)); @@ -2285,6 +2469,12 @@ void ExportXsheetPdfPopup::setInfo() { m_currentTmpl->setInfo(info); + if (m_drawDialogueCB->isChecked() && m_drawDialogueCB->isEnabled()) + m_currentTmpl->setNoteColumn(m_noteColumns.value( + m_dialogueColCombo->currentData().toInt(), nullptr)); + else + m_currentTmpl->setNoteColumn(nullptr); + if (!m_logoImgRB->isChecked()) return; // prepare logo image diff --git a/toonz/sources/toonz/exportxsheetpdf.h b/toonz/sources/toonz/exportxsheetpdf.h index e55e595b..9d467f74 100644 --- a/toonz/sources/toonz/exportxsheetpdf.h +++ b/toonz/sources/toonz/exportxsheetpdf.h @@ -21,6 +21,7 @@ class QComboBox; class QCheckBox; class TXshLevelColumn; class TXshSoundColumn; +class TXshSoundTextColumn; namespace DVGui { class FileField; class ColorField; @@ -149,6 +150,7 @@ protected: // column and column name (if manually specified) QList> m_columns; QList m_soundColumns; + TXshSoundTextColumn* m_noteColumn; int m_duration; bool m_useExtraColumns; @@ -191,6 +193,7 @@ protected: bool isBottom = false); void drawLogo(QPainter& painter); void drawSound(QPainter& painter, int framePage); + void drawDialogue(QPainter& painter, int framePage); int param(const std::string& id, int defaultValue = 0) { if (!m_params.contains(id)) std::cout << id << std::endl; @@ -214,6 +217,9 @@ public: void setSoundColumns(const QList& soundColumns) { m_soundColumns = soundColumns; } + void setNoteColumn(TXshSoundTextColumn* noteColumn) { + m_noteColumn = noteColumn; + } void setInfo(const XSheetPDFFormatInfo& info); }; @@ -275,11 +281,13 @@ class ExportXsheetPdfPopup final : public DVGui::Dialog { XsheetPdfPreviewArea* m_previewArea; DVGui::FileField* m_pathFld; QLineEdit* m_fileNameFld; - QComboBox *m_templateCombo, *m_exportAreaCombo, *m_continuousLineCombo; + QComboBox *m_templateCombo, *m_exportAreaCombo, *m_continuousLineCombo, + *m_dialogueColCombo; DVGui::ColorField* m_lineColorFld; QCheckBox *m_addDateTimeCB, *m_addScenePathCB, *m_drawSoundCB, - *m_addSceneNameCB, *m_serialFrameNumberCB, *m_levelNameOnBottomCB; + *m_addSceneNameCB, *m_serialFrameNumberCB, *m_levelNameOnBottomCB, + *m_drawDialogueCB; QFontComboBox *m_templateFontCB, *m_contentsFontCB; QTextEdit* m_memoEdit; @@ -300,6 +308,7 @@ class ExportXsheetPdfPopup final : public DVGui::Dialog { // column and column name (if manually specified) QList> m_columns; QList m_soundColumns; + QMap m_noteColumns; int m_duration; XSheetPDFTemplate* m_currentTmpl; diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index 60cafc29..bdd19eb0 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -752,67 +752,85 @@ void RenameCellField::showInRowCol(int row, int col, bool multiColumnSelected) { void RenameCellField::renameSoundTextColumn(TXshSoundTextColumn *sndTextCol, const QString &s) { - TXsheet *xsheet = m_viewer->getXsheet(); - QString oldText = "changeMe"; // text for undo - changed later - TXshCell cell = xsheet->getCell(m_row, m_col); - TXshCell oldCell = cell; - // the text index is always one less than the frame number - int textIndex = cell.getFrameId().getNumber() - 1; - if (!cell.m_level) { // cell not part of a level - oldText = ""; - int lastFrame = sndTextCol->getMaxFrame(); - TXshSoundTextLevel *sndTextLevel; - TXshCell lastCell; - TFrameId newId; - if (lastFrame < 0) { // no level on column - sndTextLevel = new TXshSoundTextLevel(); - sndTextLevel->setType(SND_TXT_XSHLEVEL); - newId = TFrameId(1); - cell = TXshCell(sndTextLevel, newId); - sndTextCol->setCell(m_row, cell); - textIndex = 0; - } else { - TXshCell lastCell = xsheet->getCell(lastFrame, m_col); - TXshSoundTextLevel *sndTextLevel = lastCell.m_level->getSoundTextLevel(); - int textSize = sndTextLevel->m_framesText.size(); - textIndex = textSize; - newId = TFrameId(textSize + 1); - cell = TXshCell(sndTextLevel, newId); - sndTextCol->setCell(m_row, cell); - } - } + if (sndTextCol->isLocked()) return; + TXsheet *xsheet = m_viewer->getXsheet(); - TXshCell prevCell = xsheet->getCell(m_row - 1, m_col); - TXshSoundTextLevel *textLevel = cell.m_level->getSoundTextLevel(); - if (oldText == "changeMe") - oldText = textLevel->getFrameText(cell.getFrameId().getNumber() - 1); - if (!prevCell.isEmpty()) { - // check if the previous cell had the same content as the entered text - // just extend the frame if so - if (textLevel->getFrameText(prevCell.getFrameId().getNumber() - 1) == s) { - sndTextCol->setCell(m_row, prevCell); - RenameTextCellUndo *undo = new RenameTextCellUndo( - m_row, m_col, oldCell, prevCell, oldText, s, textLevel); - TUndoManager::manager()->add(undo); - return; + int r0, c0, r1, c1; + TCellSelection *cellSelection = dynamic_cast( + TApp::instance()->getCurrentSelection()->getSelection()); + if (!cellSelection) { + r0 = m_row; + r1 = m_row; + } else + cellSelection->getSelectedCells(r0, c0, r1, c1); + + TUndoManager::manager()->beginBlock(); + for (int row = r0; row <= r1; row++) { + QString oldText = "changeMe"; // text for undo - changed later + TXshCell cell = xsheet->getCell(row, m_col); + TXshCell oldCell = cell; + // the text index is always one less than the frame number + int textIndex = cell.getFrameId().getNumber() - 1; + if (!cell.m_level) { // cell not part of a level + oldText = ""; + int lastFrame = sndTextCol->getMaxFrame(); + TXshSoundTextLevel *sndTextLevel; + TXshCell lastCell; + TFrameId newId; + if (lastFrame < 0) { // no level on column + sndTextLevel = new TXshSoundTextLevel(); + sndTextLevel->setType(SND_TXT_XSHLEVEL); + newId = TFrameId(1); + cell = TXshCell(sndTextLevel, newId); + sndTextCol->setCell(row, cell); + textIndex = 0; + } else { + TXshCell lastCell = xsheet->getCell(lastFrame, m_col); + TXshSoundTextLevel *sndTextLevel = + lastCell.m_level->getSoundTextLevel(); + int textSize = sndTextLevel->m_framesText.size(); + textIndex = textSize; + newId = TFrameId(textSize + 1); + cell = TXshCell(sndTextLevel, newId); + sndTextCol->setCell(row, cell); + } } - // check if the cell was part of an extended frame, but now has different - // text - else if (textLevel->getFrameText(textIndex) == - textLevel->getFrameText(prevCell.getFrameId().getNumber() - - 1) && - textLevel->getFrameText(textIndex) != s) { - int textSize = textLevel->m_framesText.size(); - textIndex = textSize; - TFrameId newId = TFrameId(textSize + 1); - cell = TXshCell(textLevel, newId); - sndTextCol->setCell(m_row, cell); + + TXshCell prevCell = xsheet->getCell(row - 1, m_col); + TXshSoundTextLevel *textLevel = cell.m_level->getSoundTextLevel(); + if (oldText == "changeMe") + oldText = textLevel->getFrameText(cell.getFrameId().getNumber() - 1); + if (!prevCell.isEmpty()) { + QString prevCellText = + textLevel->getFrameText(prevCell.getFrameId().getNumber() - 1); + // check if the previous cell had the same content as the entered text + // just extend the frame if so + // Pressing enter with empty input field will also continue the previous + // cell text. + if (prevCellText == s || s.isEmpty()) { + sndTextCol->setCell(row, prevCell); + RenameTextCellUndo *undo = new RenameTextCellUndo( + row, m_col, oldCell, prevCell, oldText, prevCellText, textLevel); + TUndoManager::manager()->add(undo); + continue; + } + // check if the cell was part of an extended frame, but now has different + // text + else if (textLevel->getFrameText(textIndex) == prevCellText && + textLevel->getFrameText(textIndex) != s) { + int textSize = textLevel->m_framesText.size(); + textIndex = textSize; + TFrameId newId = TFrameId(textSize + 1); + cell = TXshCell(textLevel, newId); + sndTextCol->setCell(row, cell); + } } + RenameTextCellUndo *undo = new RenameTextCellUndo(row, m_col, oldCell, cell, + oldText, s, textLevel); + TUndoManager::manager()->add(undo); + textLevel->setFrameText(textIndex, s); } - RenameTextCellUndo *undo = new RenameTextCellUndo(m_row, m_col, oldCell, cell, - oldText, s, textLevel); - TUndoManager::manager()->add(undo); - textLevel->setFrameText(textIndex, s); + TUndoManager::manager()->endBlock(); TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); } @@ -1337,36 +1355,40 @@ void CellArea::drawCells(QPainter &p, const QRect toBeUpdated) { isReference = false; } - // for each frame - for (row = r0; row <= r1; row++) { - if (col >= 0 && !isColumn) { - drawFrameSeparator(p, row, col, true); - if (TApp::instance()->getCurrentFrame()->isEditingScene() && - !m_viewer->orientation()->isVerticalTimeline() && - row == m_viewer->getCurrentRow() && - Preferences::instance()->isCurrentTimelineIndicatorEnabled()) { - QPoint xy = m_viewer->positionToXY(CellPosition(row, col)); - int x = xy.x(); - int y = xy.y(); - if (row == 0) { - if (m_viewer->orientation()->isVerticalTimeline()) - xy.setY(xy.y() + 1); - else - xy.setX(xy.x() + 1); + if (isSoundTextColumn) + drawSoundTextColumn(p, r0, r1, col); + else { + // for each frame + for (row = r0; row <= r1; row++) { + if (col >= 0 && !isColumn) { + drawFrameSeparator(p, row, col, true); + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !m_viewer->orientation()->isVerticalTimeline() && + row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) { + QPoint xy = m_viewer->positionToXY(CellPosition(row, col)); + int x = xy.x(); + int y = xy.y(); + if (row == 0) { + if (m_viewer->orientation()->isVerticalTimeline()) + xy.setY(xy.y() + 1); + else + xy.setX(xy.x() + 1); + } + drawCurrentTimeIndicator(p, xy); } - drawCurrentTimeIndicator(p, xy); + continue; } - continue; - } - // Cells appearance depending on the type of column - if (isSoundColumn) - drawSoundCell(p, row, col, isReference); - else if (isPaletteColumn) - drawPaletteCell(p, row, col, isReference); - else if (isSoundTextColumn) - drawSoundTextCell(p, row, col); - else - drawLevelCell(p, row, col, isReference, showLevelName); + // Cells appearance depending on the type of column + if (isSoundColumn) + drawSoundCell(p, row, col, isReference); + else if (isPaletteColumn) + drawPaletteCell(p, row, col, isReference); + // else if (isSoundTextColumn) // I left these lines just in case + // drawSoundTextCell(p, row, col); + else + drawLevelCell(p, row, col, isReference, showLevelName); + } } // draw vertical line @@ -2388,6 +2410,268 @@ void CellArea::drawSoundTextCell(QPainter &p, int row, int col) { //----------------------------------------------------------------------------- +void CellArea::drawSoundTextColumn(QPainter &p, int r0, int r1, int col) { + const Orientation *o = m_viewer->orientation(); + TXsheet *xsh = m_viewer->getXsheet(); + + struct CellInfo { + int row; + QRect rect; + QPoint xy; + int markId; + QColor markColor; + QRect markRect; + QRect nameRect; + }; + + auto getCellInfo = [&](int r) { + CellInfo ret; + ret.row = r; + ret.xy = m_viewer->positionToXY(CellPosition(r, col)); + QPoint frameAdj = m_viewer->getFrameZoomAdjustment(); + QRect cellRect = o->rect(PredefinedRect::CELL).translated(ret.xy); + cellRect.adjust(0, 0, -frameAdj.x(), -frameAdj.y()); + ret.rect = cellRect.adjusted(1, 1, 0, 1); + + ret.markId = xsh->getColumn(col)->getCellColumn()->getCellMark(r); + if (ret.markId >= 0) { + TPixel32 col = TApp::instance() + ->getCurrentScene() + ->getScene() + ->getProperties() + ->getCellMark(ret.markId) + .color; + ret.markColor = QColor(col.r, col.g, col.b, 196); // semi transparent + ret.markRect = + o->rect(PredefinedRect::CELL_MARK_AREA) + .adjusted(0, -std::round(double(frameAdj.y()) * 0.1), + -frameAdj.y(), -std::round(double(frameAdj.y()) * 0.9)) + .translated(ret.xy); + if (ret.markRect.right() > ret.rect.right()) + ret.markRect.setRight(ret.rect.right()); + } + ret.nameRect = o->rect(PredefinedRect::CELL_NAME) + .translated(ret.xy) + .adjusted(0, 0, -frameAdj.x(), -frameAdj.y()); + + return ret; + }; + + QString fontName = Preferences::instance()->getInterfaceFont(); + if (fontName == "") { +#ifdef _WIN32 + fontName = "Arial"; +#else + fontName = "Helvetica"; +#endif + } + static QFont font(fontName, -1, QFont::Normal); + font.setPixelSize(XSHEET_FONT_PX_SIZE); + static QFont largeFont(font); + largeFont.setPixelSize(XSHEET_FONT_PX_SIZE * 13 / 10); + QFontMetrics fm(font); + int heightThres = QFontMetrics(largeFont).height(); + + int rStart = r0; + int rEnd = r1; + // obtain top row of the fist note block + if (!xsh->getCell(r0, col).isEmpty()) { + while (rStart > 0 && + xsh->getCell(rStart - 1, col) == xsh->getCell(r0, col)) { + rStart--; + } + } + // obtain bottom row of the last note block + if (!xsh->getCell(r1, col).isEmpty()) { + while (xsh->getCell(rEnd + 1, col) == xsh->getCell(r1, col)) { + rEnd++; + } + } + + QColor cellColor = m_viewer->getSoundTextColumnColor(); + QColor selectedCellColor = m_viewer->getSelectedSoundTextColumnColor(); + QColor sideColor = m_viewer->getSoundTextColumnBorderColor(); + + TCellSelection *cellSelection = m_viewer->getCellSelection(); + TColumnSelection *columnSelection = m_viewer->getColumnSelection(); + bool isColSelected = columnSelection->isColumnSelected(col); + + // for each row + for (int row = rStart; row <= rEnd; row++) { + TXshCell cell = xsh->getCell(row, col); + + // if the cell is empty + if (cell.isEmpty()) { + CellInfo info = getCellInfo(row); + drawFrameSeparator(p, row, col, true); + // draw X shape after the occupied cell + TXshCell prevCell; + if (row > 0) prevCell = xsh->getCell(row - 1, col); + if (!prevCell.isEmpty()) { + QColor levelEndColor = m_viewer->getTextColor(); + levelEndColor.setAlphaF(0.3); + p.setPen(levelEndColor); + p.drawLine(info.rect.topLeft(), info.rect.bottomRight()); + p.drawLine(info.rect.topRight(), info.rect.bottomLeft()); + } + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !m_viewer->orientation()->isVerticalTimeline() && + row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) + drawCurrentTimeIndicator(p, info.xy); + // draw mark + if (info.markId >= 0) { + p.setBrush(info.markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(info.markRect); + } + continue; + } + + //---- if the current cell is occupied + + QList infoList; + // check how long the same content continues + int rowTo = row; + infoList.append(getCellInfo(row)); + while (xsh->getCell(rowTo + 1, col) == cell) { + rowTo++; + infoList.append(getCellInfo(rowTo)); + } + + // for each cell block with the same content + + // paint background and other stuffs + for (auto info : infoList) { + bool heldFrame = (!o->isVerticalTimeline() && info.row != row); + drawFrameSeparator(p, info.row, col, false, heldFrame); + + bool isSelected = + isColSelected || cellSelection->isCellSelected(info.row, col); + QColor tmpCellColor = (isSelected) ? selectedCellColor : cellColor; + if (!o->isVerticalTimeline() && info.row != rowTo) + info.rect.adjust(0, 0, 2, 0); + p.fillRect(info.rect, QBrush(tmpCellColor)); + + if (TApp::instance()->getCurrentFrame()->isEditingScene() && + !o->isVerticalTimeline() && info.row == m_viewer->getCurrentRow() && + Preferences::instance()->isCurrentTimelineIndicatorEnabled()) + drawCurrentTimeIndicator(p, info.xy); + + drawDragHandle(p, info.xy, sideColor); + drawEndOfDragHandle(p, info.row == rowTo, info.xy, tmpCellColor); + drawLockedDottedLine(p, xsh->getColumn(col)->isLocked(), info.xy, + tmpCellColor); + // draw mark + if (info.markId >= 0) { + p.setBrush(info.markColor); + p.setPen(Qt::NoPen); + p.drawEllipse(info.markRect); + } + } + + // draw text from here + + QString text = + cell.getSoundTextLevel()->getFrameText(cell.m_frameId.getNumber() - 1); + if (text.isEmpty()) { + // advance the current row + row = rowTo; + continue; + } + int textCount = text.count(); + + p.setPen(Qt::black); + // Vertical case + if (o->isVerticalTimeline()) { + int lettersPerChunk = + (int)std::ceil((double)textCount / (double)(rowTo - row + 1)); + int chunkCount = + (int)std::ceil((double)textCount / (double)(lettersPerChunk)); + bool isChunkOverflow = false; + for (int c = 0; c < chunkCount; c++) { + int chunkWidth = + fm.boundingRect(text.mid(c * lettersPerChunk, lettersPerChunk)) + .width(); + if (chunkWidth > infoList.front().nameRect.width()) { + isChunkOverflow = true; + break; + } + } + // if any chunk overflows the cell width + if (isChunkOverflow) { + p.setFont(font); + // arrange text from the top cell and elide at the last cell + int textPos = 0; + for (auto info : infoList) { + // add letter and check if the text can be inside the cell + int len = 1; + while (textPos + len < textCount && + fm.boundingRect(text.mid(textPos, len + 1)).width() <= + info.nameRect.width()) { + len++; + } + // elide text at the last row + QString curText = + (info.row == rowTo) + ? elideText(text.mid(textPos), fm, info.nameRect.width(), "~") + : text.mid(textPos, len); + + p.drawText(info.nameRect, Qt::AlignCenter, curText); + textPos += len; + if (textPos >= textCount) break; + } + } + // if all text chunks can be inside the cells + else { + // unite the cell rects and divide by the amount of chunks + QRect unitedRect = + infoList.front().nameRect.united(infoList.last().nameRect); + // check if the large font is available + if (lettersPerChunk == 1 && + unitedRect.height() / textCount > heightThres) + p.setFont(largeFont); + else + p.setFont(font); + // draw text + for (int c = 0; c < chunkCount; c++) { + int y0 = unitedRect.top() + unitedRect.height() * c / chunkCount; + int y1 = + unitedRect.top() + unitedRect.height() * (c + 1) / chunkCount; + QRect tmpRect(unitedRect.left(), y0, unitedRect.width(), y1 - y0 + 1); + p.drawText(tmpRect, Qt::AlignCenter, + text.mid(c * lettersPerChunk, lettersPerChunk)); + } + } + } + // Horizontal case + else { + p.setFont(font); + // unite the cell rects + QRect unitedRect = + infoList.front().nameRect.united(infoList.last().nameRect); + int extraWidth = unitedRect.width() - fm.boundingRect(text).width(); + if (extraWidth >= 0) { + int margin = extraWidth / (2 * textCount); + // Qt::TextJustificationForced flag is needed to make Qt::AlignJustify + // to work on the single-line text + p.drawText( + unitedRect.adjusted(margin, 0, -margin, 0), + Qt::TextJustificationForced | Qt::AlignJustify | Qt::AlignVCenter, + text); + } else { + QString elided = elideText(text, fm, unitedRect.width(), "~"); + p.drawText(unitedRect, Qt::AlignLeft | Qt::AlignVCenter, elided); + } + } + + // advance the current row + row = rowTo; + } +} + +//----------------------------------------------------------------------------- + void CellArea::drawPaletteCell(QPainter &p, int row, int col, bool isReference) { const Orientation *o = m_viewer->orientation(); diff --git a/toonz/sources/toonz/xshcellviewer.h b/toonz/sources/toonz/xshcellviewer.h index 1b3e3e9a..110d7848 100644 --- a/toonz/sources/toonz/xshcellviewer.h +++ b/toonz/sources/toonz/xshcellviewer.h @@ -118,6 +118,7 @@ class CellArea final : public QWidget { bool showLevelName = true); void drawSoundTextCell(QPainter &p, int row, int col); void drawSoundCell(QPainter &p, int row, int col, bool isReference = false); + void drawSoundTextColumn(QPainter &p, int r0, int r1, int col); void drawPaletteCell(QPainter &p, int row, int col, bool isReference = false); void drawKeyframe(QPainter &p, const QRect toBeUpdated); diff --git a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp index 65e47526..8226e434 100644 --- a/toonz/sources/toonzlib/txshsoundtextcolumn.cpp +++ b/toonz/sources/toonzlib/txshsoundtextcolumn.cpp @@ -68,14 +68,22 @@ void TXshSoundTextColumn::loadData(TIStream &is) { if (tagName == "cells") { while (is.openChild(tagName)) { if (tagName == "cell") { - TPersist *p = 0; - int row = 1; - int fidNumber = 1; + TPersist *p = 0; + std::string rowRangeStr = "1"; + int fidNumber = 1; TFilePath path; - is >> row >> fidNumber >> p; + is >> rowRangeStr >> fidNumber >> p; TXshLevel *xshLevel = dynamic_cast(p); TXshCell cell(xshLevel, TFrameId(fidNumber)); - setCell(row, cell); + + QString _rowRangeStr = QString::fromStdString(rowRangeStr); + QStringList rows = _rowRangeStr.split('-'); + if (rows.size() == 1) + setCell(rows[0].toInt(), cell); + else if (rows.size() == 2) { + for (int r = rows[0].toInt(); r <= rows[1].toInt(); r++) + setCell(r, cell); + } } else throw TException("TXshLevelColumn, unknown tag(2): " + tagName); is.closeChild(); @@ -94,11 +102,42 @@ void TXshSoundTextColumn::saveData(TOStream &os) { int r0, r1; if (getRange(r0, r1)) { os.openChild("cells"); + TXshCell prevCell; + int fromR = r0; for (int r = r0; r <= r1; r++) { TXshCell cell = getCell(r); - if (cell.isEmpty()) continue; - TFrameId fid = cell.m_frameId; - os.child("cell") << r << fid.getNumber() << cell.m_level.getPointer(); + + if (cell != prevCell) { + if (!prevCell.isEmpty()) { + int toR = r - 1; + TFrameId fid = prevCell.m_frameId; + if (fromR == toR) + os.child("cell") + << toR << fid.getNumber() << prevCell.m_level.getPointer(); + else { + QString rangeStr = QString("%1-%2").arg(fromR).arg(toR); + os.child("cell") << rangeStr.toStdString() << fid.getNumber() + << prevCell.m_level.getPointer(); + } + } + prevCell = cell; + fromR = r; + } + assert(cell == prevCell); + if (r == r1) { + if (!cell.isEmpty()) { + int toR = r; + TFrameId fid = cell.m_frameId; + if (fromR == toR) + os.child("cell") + << toR << fid.getNumber() << cell.m_level.getPointer(); + else { + QString rangeStr = QString("%1-%2").arg(fromR).arg(toR); + os.child("cell") << rangeStr.toStdString() << fid.getNumber() + << cell.m_level.getPointer(); + } + } + } } os.closeChild(); } From 66dd8c0bd28465de3c4fa5ea25c4770d31b911e9 Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Thu, 23 Dec 2021 00:43:03 +0900 Subject: [PATCH 18/18] fix shift key loses focus when renaming cell --- toonz/sources/toonz/xshcellviewer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index bdd19eb0..b0fdcdfd 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -1122,6 +1122,11 @@ void RenameCellField::keyPressEvent(QKeyEvent *event) { } offset = m_viewer->orientation()->arrowShift(key); break; + case Qt::Key_Shift: + // prevent the field to lose focus when typing shift key + event->accept(); + return; + break; default: QLineEdit::keyPressEvent(event); return;