#include "expressionreferencemanager.h"
#include "tapp.h"
// TnzQt includes
#include "toonzqt/dvdialog.h"
// TnzLib includes
#include "toonz/txsheethandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/txsheetexpr.h"
#include "toonz/doubleparamcmd.h"
#include "toonz/preferences.h"
#include "toonz/tstageobject.h"
#include "toonz/tcolumnfx.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/fxdag.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/toonzscene.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshcell.h"
#include "toonz/txshchildlevel.h"
#include "toonz/tstageobjecttree.h"
// TnzBase includes
#include "tdoubleparam.h"
#include "texpression.h"
#include "tdoublekeyframe.h"
#include "tfx.h"
#include "tmsgcore.h"
#include <QList>
#include <boost/xpressive/xpressive_static.hpp>
#include <boost/xpressive/regex_actions.hpp>
namespace {
// reference : columncommand.cpp
bool canRemoveFx(const std::set<TFx*>& leaves, TFx* fx) {
bool removeFx = false;
for (int i = 0; i < fx->getInputPortCount(); i++) {
TFx* inputFx = fx->getInputPort(i)->getFx();
if (!inputFx) continue;
if (leaves.count(inputFx) > 0) {
removeFx = true;
continue;
}
if (!canRemoveFx(leaves, inputFx)) return false;
removeFx = true;
}
return removeFx;
}
void gatherXsheets(TXsheet* xsheet, QSet<TXsheet*>& ret) {
// return if it is already registered
if (ret.contains(xsheet)) return;
ret.insert(xsheet);
// trace xsheet and recursively find sub-xsheets in it
for (int c = 0; c < xsheet->getColumnCount(); c++) {
if (xsheet->isColumnEmpty(c)) continue;
TXshLevelColumn* levelColumn = xsheet->getColumn(c)->getLevelColumn();
if (!levelColumn) continue;
int start, end;
levelColumn->getRange(start, end);
for (int r = start; r <= end; r++) {
int r0, r1;
if (!levelColumn->getLevelRange(r, r0, r1)) continue;
TXshChildLevel* childLevel =
levelColumn->getCell(r).m_level->getChildLevel();
if (childLevel) {
gatherXsheets(childLevel->getXsheet(), ret);
}
r = r1;
}
}
}
QSet<TXsheet*> getAllXsheets() {
QSet<TXsheet*> ret;
TXsheet* topXsheet =
TApp::instance()->getCurrentScene()->getScene()->getTopXsheet();
gatherXsheets(topXsheet, ret);
return ret;
}
static QList<QList<std::string>> objExprPhrases = {
{"table", "tab"}, // Table
{"col"}, // Column
{"cam", "camera"}, // Camera
{"peg", "pegbar"}, // Pegbar
{} // Spline and others
};
int getObjTypeIndex(TStageObjectId id) {
if (id.isTable())
return 0;
else if (id.isColumn())
return 1;
else if (id.isCamera())
return 2;
else if (id.isPegbar())
return 3;
else
return 4;
}
int getPhraseCount(TStageObjectId id) {
return objExprPhrases[getObjTypeIndex(id)].count();
}
std::string getPhrase(TStageObjectId id, int index = 0) {
if (getPhraseCount(id) <= index) index = 0;
std::string indexStr =
(id.isTable()) ? "" : std::to_string(id.getIndex() + 1);
// including period to avoid misunderstanding "col10" as "col1"
return objExprPhrases[getObjTypeIndex(id)][index] + indexStr + ".";
}
std::string getPhrase(TFx* fx) {
QString fxIdStr = QString::fromStdWString(toLower(fx->getFxId()));
return "fx." + fxIdStr.toStdString() + ".";
}
} // namespace
//----------------------------------------------------------------------------
ExpressionReferenceMonitor* ExpressionReferenceManager::currentMonitor() {
return TApp::instance()->getCurrentXsheet()->getXsheet()->getExpRefMonitor();
}
QMap<TDoubleParam*, ExpressionReferenceMonitorInfo>&
ExpressionReferenceManager::info(TXsheet* xsh) {
if (xsh)
return xsh->getExpRefMonitor()->info();
else
return currentMonitor()->info();
}
//-----------------------------------------------------------------------------
ExpressionReferenceMonitorInfo& ExpressionReferenceManager::touchInfo(
TDoubleParam* param, TXsheet* xsh) {
if (!info(xsh).contains(param)) {
ExpressionReferenceMonitorInfo newInfo;
info(xsh).insert(param, newInfo);
param->addObserver(this);
}
return info(xsh)[param];
}
//-----------------------------------------------------------------------------
ExpressionReferenceManager::ExpressionReferenceManager()
: m_model(new FunctionTreeModel()), m_blockParamChange(false) {}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::init() {
connect(TApp::instance()->getCurrentScene(),
SIGNAL(preferenceChanged(const QString&)), this,
SLOT(onPreferenceChanged(const QString&)));
onPreferenceChanged("modifyExpressionOnMovingReferences");
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::onPreferenceChanged(const QString& prefName) {
if (prefName != "modifyExpressionOnMovingReferences") return;
TXsheetHandle* xshHandle = TApp::instance()->getCurrentXsheet();
TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene();
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (on) {
// when the scene switched, refresh the all list
connect(sceneHandle, SIGNAL(sceneSwitched()), this,
SLOT(onSceneSwitched()));
connect(xshHandle, SIGNAL(xsheetSwitched()), this,
SLOT(onXsheetSwitched()));
connect(xshHandle, SIGNAL(xsheetChanged()), this, SLOT(onXsheetChanged()));
onSceneSwitched();
} else {
// when the scene switched, refresh the all list
disconnect(sceneHandle, SIGNAL(sceneSwitched()), this,
SLOT(onSceneSwitched()));
disconnect(xshHandle, SIGNAL(xsheetSwitched()), this,
SLOT(onXsheetSwitched()));
disconnect(xshHandle, SIGNAL(xsheetChanged()), this,
SLOT(onXsheetChanged()));
// clear all monitor info
QSet<TXsheet*> allXsheets = getAllXsheets();
for (auto xsh : allXsheets) {
for (auto curve : xsh->getExpRefMonitor()->info().keys())
curve->removeObserver(this);
xsh->getExpRefMonitor()->clearAll();
xsh->setObserver(nullptr);
}
}
}
//-----------------------------------------------------------------------------
ExpressionReferenceManager* ExpressionReferenceManager::instance() {
static ExpressionReferenceManager _instance;
return &_instance;
}
//-----------------------------------------------------------------------------
bool ExpressionReferenceManager::refreshParamsRef(TDoubleParam* curve,
TXsheet* xsh) {
QSet<int> colRef;
QSet<TDoubleParam*> paramsRef;
for (int k = 0; k < curve->getKeyframeCount(); k++) {
TDoubleKeyframe keyframe = curve->getKeyframe(k);
if (keyframe.m_type != TDoubleKeyframe::Expression &&
keyframe.m_type != TDoubleKeyframe::SimilarShape)
continue;
TExpression expr;
expr.setGrammar(curve->getGrammar());
expr.setText(keyframe.m_expressionText);
QSet<int> tmpColRef;
QSet<TDoubleParam*> tmpParamsRef;
referenceParams(expr, tmpColRef, tmpParamsRef);
colRef += tmpColRef;
paramsRef += tmpParamsRef;
}
// replace the indices
bool hasRef = !colRef.isEmpty() || !paramsRef.isEmpty();
if (hasRef) {
touchInfo(curve, xsh).colRefMap() = colRef;
touchInfo(curve, xsh).paramRefMap() = paramsRef;
} else {
info(xsh).remove(curve);
}
return hasRef;
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::checkRef(TreeModel::Item* item, TXsheet* xsh) {
if (FunctionTreeModel::Channel* channel =
dynamic_cast<FunctionTreeModel::Channel*>(item)) {
TDoubleParam* curve = channel->getParam();
bool hasRef = refreshParamsRef(curve, xsh);
if (hasRef) touchInfo(curve, xsh).name() = channel->getLongName();
} else
for (int i = 0; i < item->getChildCount(); i++)
checkRef(item->getChild(i), xsh);
}
//-----------------------------------------------------------------------------
FunctionTreeModel::Channel* ExpressionReferenceManager::findChannel(
TDoubleParam* param, TreeModel::Item* item) {
if (FunctionTreeModel::Channel* channel =
dynamic_cast<FunctionTreeModel::Channel*>(item)) {
if (channel->getParam() == param) return channel;
} else {
for (int i = 0; i < item->getChildCount(); i++) {
FunctionTreeModel::Channel* ret = findChannel(param, item->getChild(i));
if (ret) return ret;
}
}
return nullptr;
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::gatherParams(TreeModel::Item* item,
QList<TDoubleParam*>& paramSet) {
if (FunctionTreeModel::Channel* channel =
dynamic_cast<FunctionTreeModel::Channel*>(item)) {
paramSet.append(channel->getParam());
} else
for (int i = 0; i < item->getChildCount(); i++)
gatherParams(item->getChild(i), paramSet);
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::onSceneSwitched() {
QSet<TXsheet*> allXsheets = getAllXsheets();
for (auto xsh : allXsheets) {
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::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);
m_model->refreshData(xsh);
}
//----------------------------------------------------------------------------
void ExpressionReferenceManager::onXsheetChanged() {
TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
m_model->refreshData(xsh);
// remove deleted parameters
QList<TDoubleParam*> paramSet;
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
gatherParams(m_model->getStageObjectChannel(i), paramSet);
}
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
gatherParams(m_model->getFxChannel(i), paramSet);
}
// remove deleted parameter from reference map
for (auto itr = info(xsh).begin(); itr != info(xsh).end();) {
if (!paramSet.contains(itr.key()))
itr = info(xsh).erase(itr);
else {
// check if the referenced parameters are deleted
if (!itr.value().ignored()) {
for (auto refParam : itr.value().paramRefMap()) {
if (!paramSet.contains(refParam)) {
// ignore the parameter if the reference does not exist anymore
itr.value().ignored() = true;
break;
}
}
}
++itr;
}
}
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::onChange(const TXsheetColumnChange& change) {
TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
QMap<TStageObjectId, TStageObjectId> idTable;
auto setIds = [&](int from, int to) {
idTable.insert(TStageObjectId::ColumnId(from),
TStageObjectId::ColumnId(to));
};
switch (change.m_type) {
case TXsheetColumnChange::Insert: {
for (int c = xsh->getColumnCount() - 2; c >= change.m_index1; c--) {
setIds(c, c + 1);
}
} break;
case TXsheetColumnChange::Remove: {
// update ignore info
for (auto it = info().begin(); it != info().end(); it++) {
if (it.value().colRefMap().contains(change.m_index1))
it.value().ignored() = true;
}
for (int c = change.m_index1; c < xsh->getColumnCount(); c++) {
setIds(c + 1, c);
}
} break;
case TXsheetColumnChange::Move: {
if (change.m_index1 < change.m_index2) {
setIds(change.m_index1, change.m_index2);
for (int c = change.m_index1 + 1; c <= change.m_index2; c++) {
setIds(c, c - 1);
}
} else {
setIds(change.m_index1, change.m_index2);
for (int c = change.m_index2; c < change.m_index1; c++) {
setIds(c, c + 1);
}
}
} break;
}
// use empty map since the fxs does not transfer
QMap<TFx*, TFx*> fxTable;
transferReference(xsh, xsh, idTable, fxTable);
}
void ExpressionReferenceManager::onFxAdded(const std::vector<TFx*>& fxs) {
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
FxChannelGroup* fcg =
dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
if (fcg && fxs.end() != std::find(fxs.begin(), fxs.end(), fcg->getFx()))
checkRef(fcg);
}
}
void ExpressionReferenceManager::onStageObjectAdded(
const TStageObjectId objId) {
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
m_model->getStageObjectChannel(i));
if (socg && objId == socg->getStageObject()->getId()) checkRef(socg);
}
}
bool ExpressionReferenceManager::isIgnored(TDoubleParam* param) {
return touchInfo(param).ignored();
}
//-----------------------------------------------------------------------------
// TParamObserver implementation
void ExpressionReferenceManager::onChange(const TParamChange& change) {
// do nothing if the change is due to this manager itself
if (m_blockParamChange) return;
// do nothing if keyframe does not change or while dragging
if (!change.m_keyframeChanged || change.m_dragging) return;
TDoubleParam* curve = dynamic_cast<TDoubleParam*>(change.m_param);
if (!curve) return;
bool hasRef = refreshParamsRef(curve, nullptr);
if (hasRef) {
FunctionTreeModel::Channel* channel = nullptr;
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
channel = findChannel(curve, m_model->getStageObjectChannel(i));
if (channel) break;
}
if (!channel) {
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
channel = findChannel(curve, m_model->getFxChannel(i));
if (channel) break;
}
}
if (channel) {
touchInfo(curve).name() = channel->getLongName();
}
if (touchInfo(curve).ignored()) {
DVGui::info(tr("Expression monitoring restarted: \"%1\"")
.arg(touchInfo(curve).name()));
touchInfo(curve).ignored() = false;
}
}
}
//-----------------------------------------------------------------------------
void ExpressionReferenceManager::replaceExpressionTexts(
TDoubleParam* curve, const std::map<std::string, std::string> replaceMap,
TXsheet* xsh) {
if (touchInfo(curve, xsh).ignored() || replaceMap.empty()) {
for (int kIndex = 0; kIndex < curve->getKeyframeCount(); kIndex++) {
TDoubleKeyframe keyframe = curve->getKeyframe(kIndex);
if (keyframe.m_type != TDoubleKeyframe::Expression &&
keyframe.m_type != TDoubleKeyframe::SimilarShape)
continue;
// check circular reference
TExpression expr;
expr.setGrammar(curve->getGrammar());
expr.setText(keyframe.m_expressionText);
// put "?" marks on both ends of the expression text when the circular
// reference is detected in order to avoid crash
if (dependsOn(expr, curve))
keyframe.m_expressionText = "?" + keyframe.m_expressionText + "?";
m_blockParamChange = true;
KeyframeSetter setter(curve, kIndex, false);
if (keyframe.m_type == TDoubleKeyframe::Expression)
setter.setExpression(keyframe.m_expressionText);
else // SimilarShape case
setter.setSimilarShape(keyframe.m_expressionText,
keyframe.m_similarShapeOffset);
m_blockParamChange = false;
}
return;
}
boost::xpressive::local<std::string const*> pstr;
const boost::xpressive::sregex rx =
(boost::xpressive::a1 = replaceMap)[pstr = &boost::xpressive::a1];
for (int kIndex = 0; kIndex < curve->getKeyframeCount(); kIndex++) {
TDoubleKeyframe keyframe = curve->getKeyframe(kIndex);
if (keyframe.m_type != TDoubleKeyframe::Expression &&
keyframe.m_type != TDoubleKeyframe::SimilarShape)
continue;
// replace expression
QString expr = QString::fromStdString(keyframe.m_expressionText);
QStringList list = expr.split('"');
bool isStringToken = false;
for (QString& partialExp : list) {
if (isStringToken) continue;
isStringToken = !isStringToken;
std::string partialExpStr = partialExp.toStdString();
std::string replacedStr =
boost::xpressive::regex_replace(partialExpStr, rx, *pstr);
partialExp = QString::fromStdString(replacedStr);
}
QString newExpr = list.join('"');
m_blockParamChange = true;
KeyframeSetter setter(curve, kIndex, false);
if (keyframe.m_type == TDoubleKeyframe::Expression)
setter.setExpression(newExpr.toStdString());
else // SimilarShape case
setter.setSimilarShape(newExpr.toStdString(),
keyframe.m_similarShapeOffset);
m_blockParamChange = false;
if (newExpr != expr) {
DVGui::info(tr("Expression modified: \"%1\" key at frame %2, %3 -> %4")
.arg(touchInfo(curve, xsh).name())
.arg(keyframe.m_frame + 1)
.arg(expr)
.arg(newExpr));
}
}
}
//-----------------------------------------------------------------------------
bool ExpressionReferenceManager::doCheckReferenceDeletion(
const QSet<int>& columnIdsToBeDeleted, const QSet<TFx*>& fxsToBeDeleted,
const QList<TStageObjectId>& objectIdsToBeDeleted,
const QList<TStageObjectId>& objIdsToBeDuplicated, bool checkInvert) {
QList<TDoubleParam*> paramsToBeDeleted;
QList<TDoubleParam*> invParamsToBeDeleted;
// gather Fx parameters to be deleted
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
FxChannelGroup* fcg =
dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
if (!fcg) continue;
if (fxsToBeDeleted.contains(fcg->getFx()))
gatherParams(fcg, paramsToBeDeleted);
else if (checkInvert)
gatherParams(fcg, invParamsToBeDeleted);
}
// gather stage objects parameters to be deleted
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
m_model->getStageObjectChannel(i));
if (!socg) continue;
TStageObjectId id = socg->getStageObject()->getId();
if (objectIdsToBeDeleted.contains(id))
gatherParams(socg, paramsToBeDeleted);
// objects to be duplicated in the sub xsheet will not lose referenced from
// either xsheet
else if (checkInvert && !objIdsToBeDuplicated.contains(id))
gatherParams(socg, invParamsToBeDeleted);
}
// gather parameters which refers to the parameters to be deleted
QSet<TDoubleParam*> cautionParams;
QSet<TDoubleParam*> invCautionParams;
for (auto itr = info().begin(); itr != info().end(); itr++) {
// find params containing columnId to be deleted
for (auto refColId : itr.value().colRefMap()) {
if (columnIdsToBeDeleted.contains(refColId))
cautionParams.insert(itr.key());
else if (checkInvert)
invCautionParams.insert(itr.key());
}
// find params containing fx/stage params to be deleted as well
for (auto refParam : itr.value().paramRefMap()) {
if (paramsToBeDeleted.contains(refParam)) cautionParams.insert(itr.key());
if (checkInvert && invParamsToBeDeleted.contains(refParam))
invCautionParams.insert(itr.key());
}
}
// remove parameters from the list which itself will be deleted
for (auto it = cautionParams.begin(); it != cautionParams.end();)
if (paramsToBeDeleted.contains(*it))
it = cautionParams.erase(it);
else
++it;
for (auto it = invCautionParams.begin(); it != invCautionParams.end();)
if (invParamsToBeDeleted.contains(*it))
it = invCautionParams.erase(it);
else
++it;
// return true if there is no parameters which will lose references
if (cautionParams.isEmpty() && invCautionParams.isEmpty()) return true;
// open warning popup
QString warningTxt =
tr("Following parameters will lose reference in expressions:");
for (auto param : cautionParams) {
warningTxt += "\n " + touchInfo(param).name();
}
for (auto param : invCautionParams) {
warningTxt += "\n " + touchInfo(param).name() + " " +
tr("(To be in the sub xsheet)");
}
warningTxt += "\n" + tr("Do you want to continue the operation anyway ?");
int ret = DVGui::MsgBox(warningTxt, QObject::tr("Continue"),
QObject::tr("Cancel"), 0);
if (ret == 0 || ret == 2) return false;
return true;
}
//-----------------------------------------------------------------------------
// check on deleting columns
bool ExpressionReferenceManager::checkReferenceDeletion(
const QSet<int>& columnIdsToBeDeleted, const QSet<TFx*>& fxsToBeDeleted,
const QList<TStageObjectId>& objIdsToBeDuplicated, bool checkInvert) {
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (!on) return true;
QList<TStageObjectId> objectIdsToBeDeleted;
for (auto colId : columnIdsToBeDeleted)
objectIdsToBeDeleted.append(TStageObjectId::ColumnId(colId));
return doCheckReferenceDeletion(columnIdsToBeDeleted, fxsToBeDeleted,
objectIdsToBeDeleted, objIdsToBeDuplicated,
checkInvert);
}
//-----------------------------------------------------------------------------
// check on deleting stage objects
bool ExpressionReferenceManager::checkReferenceDeletion(
const QList<TStageObjectId>& objectIdsToBeDeleted) {
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (!on) return true;
QSet<int> columnIdsToBeDeleted;
QSet<TFx*> fxsToBeDeleted;
TApp* app = TApp::instance();
TXsheet* xsh = app->getCurrentXsheet()->getXsheet();
std::set<TFx*> leaves;
// fx references should be checked when deleting columns
for (const auto& objId : objectIdsToBeDeleted) {
if (objId.isColumn()) {
int index = objId.getIndex();
if (index < 0) continue;
TXshColumn* column = xsh->getColumn(index);
if (!column) continue;
columnIdsToBeDeleted.insert(index);
TFx* fx = column->getFx();
if (fx) {
leaves.insert(fx);
TZeraryColumnFx* zcfx = dynamic_cast<TZeraryColumnFx*>(fx);
if (zcfx) fxsToBeDeleted.insert(zcfx->getZeraryFx());
}
}
}
// store fx to be deleted along with columns
TFxSet* fxSet = xsh->getFxDag()->getInternalFxs();
for (int i = 0; i < fxSet->getFxCount(); i++) {
TFx* fx = fxSet->getFx(i);
if (canRemoveFx(leaves, fx)) fxsToBeDeleted.insert(fx);
}
QList<TStageObjectId> dummy;
return doCheckReferenceDeletion(columnIdsToBeDeleted, fxsToBeDeleted,
objectIdsToBeDeleted, dummy);
}
//-----------------------------------------------------------------------------
// check on exploding sub xsheet.
// - If removeColumn is true, it means that the sub xsheet column in the parent
// xsheet will be deleted.
// - If columnsOnly is true, it means that all references to the objects other
// than columns in the sub xsheet will be lost.
// - If columnsOnly is false, it means that references to camera not connected
// to the table node in the sub xsheet will be lost.
// - Open warning popup if there is any expression which will lose reference
// after the operation.
bool ExpressionReferenceManager::checkExplode(TXsheet* childXsh, int index,
bool removeColumn,
bool columnsOnly) {
// return if the preference option is off
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (!on) return true;
QSet<TDoubleParam*> mainCautionParams, subCautionParams;
if (removeColumn) {
// find params referring to the sub xsheet column to be exploded and removed
for (auto itr = info().begin(); itr != info().end(); itr++) {
if (itr.value().colRefMap().contains(index))
mainCautionParams.insert(itr.key());
}
}
m_model->refreshData(childXsh);
// find params referring to the stage params to be deleted
QList<TDoubleParam*> stageParamsToBeDeleted;
TStageObject* table = childXsh->getStageObject(TStageObjectId::TableId);
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
m_model->getStageObjectChannel(i));
if (!socg) continue;
TStageObjectId id = socg->getStageObject()->getId();
if ((columnsOnly && !id.isColumn()) ||
(!columnsOnly && !socg->getStageObject()->isAncestor(table)))
gatherParams(socg, stageParamsToBeDeleted);
}
for (auto itr = info(childXsh).begin(); itr != info(childXsh).end(); itr++) {
for (auto refParam : itr.value().paramRefMap()) {
if (stageParamsToBeDeleted.contains(refParam)) {
subCautionParams.insert(itr.key());
break;
}
}
}
// remove parameters from the list which itself will be deleted
for (auto it = subCautionParams.begin(); it != subCautionParams.end();)
if (stageParamsToBeDeleted.contains(*it))
it = subCautionParams.erase(it);
else
++it;
TXsheet* currentXsh = TApp::instance()->getCurrentXsheet()->getXsheet();
m_model->refreshData(currentXsh);
// return true if there is no parameters which will lose references
if (mainCautionParams.isEmpty() && subCautionParams.isEmpty()) return true;
// open warning popup
QString warningTxt =
tr("Following parameters will lose reference in expressions:");
for (auto param : mainCautionParams) {
warningTxt +=
"\n " + touchInfo(param).name() + " " + tr("(In the current xsheet)");
}
for (auto param : subCautionParams) {
warningTxt += "\n " + touchInfo(param, childXsh).name() + " " +
tr("(To be brought from the subxsheet)");
}
warningTxt += tr("\nDo you want to explode anyway ?");
int ret = DVGui::MsgBox(warningTxt, QObject::tr("Explode"),
QObject::tr("Cancel"), 0);
if (ret == 0 || ret == 2) return false;
return true;
}
//----------------------------------------------------------------------------
void ExpressionReferenceManager::transferReference(
TXsheet* fromXsh, TXsheet* toXsh,
const QMap<TStageObjectId, TStageObjectId>& idTable,
const QMap<TFx*, TFx*>& fxTable) {
// return if the preference option is off
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (!on) return;
// 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.
QMap<int, int> colIdReplaceTable;
QMap<TDoubleParam*, TDoubleParam*> curveReplaceTable;
std::map<std::string, std::string> exprReplaceTable;
bool sameXSheet = (fromXsh == toXsh);
// First, check the stage objects
for (auto obj_itr = idTable.constBegin(); obj_itr != idTable.constEnd();
obj_itr++) {
TStageObjectId fromId = obj_itr.key();
TStageObjectId toId = obj_itr.value();
// register column indices replacement table ( register even if fromId and
// toId are identical )
if (fromId.isColumn() && toId.isColumn())
colIdReplaceTable.insert(fromId.getIndex(), toId.getIndex());
// register expression texts replacement table ( register only if the
// phrases will be changed )
if (fromId != toId) {
for (int ph = 0; ph < getPhraseCount(fromId); ph++)
exprReplaceTable[getPhrase(fromId, ph)] = getPhrase(toId, ph);
}
if (sameXSheet) {
// the parameter pointers are already swapped when moving columns in the
// same xsheet curveReplaceTable will be used just for parameter list to
// be modified
TStageObject* toObj =
toXsh->getStageObjectTree()->getStageObject(toId, false);
assert(toObj);
if (toObj) {
for (int c = 0; c < TStageObject::T_ChannelCount; c++) {
TDoubleParam* to_p = toObj->getParam((TStageObject::Channel)c);
curveReplaceTable.insert(to_p, to_p);
}
}
} else { // for transferring objects over xsheets (i.e. collapse and
// explode)
// register to the parameter pointer replacement table
TStageObject* fromObj =
fromXsh->getStageObjectTree()->getStageObject(fromId, false);
TStageObject* toObj =
toXsh->getStageObjectTree()->getStageObject(toId, false);
assert(fromObj && toObj);
if (fromObj && toObj) {
for (int c = 0; c < TStageObject::T_ChannelCount; c++) {
TDoubleParam* from_p = fromObj->getParam((TStageObject::Channel)c);
TDoubleParam* to_p = toObj->getParam((TStageObject::Channel)c);
curveReplaceTable.insert(from_p, to_p);
}
}
}
}
// Secondly, check the Fxs
QMap<TFx*, QList<TDoubleParam*>> fromFxParams, toFxParams;
for (auto fx_itr = fxTable.constBegin(); fx_itr != fxTable.constEnd();
fx_itr++) {
TFx* fromFx = fx_itr.key();
TFx* toFx = fx_itr.value();
// skip the case that the Xsheet node is converted to the OverFx when
// exploding
if (fromFx->getFxType() == toFx->getFxType()) {
fromFxParams.insert(fromFx, QList<TDoubleParam*>());
toFxParams.insert(toFx, QList<TDoubleParam*>());
// register expression texts replacement table
if (fromFx->getFxId() != toFx->getFxId())
exprReplaceTable[getPhrase(fromFx)] = getPhrase(toFx);
}
}
if (!fromFxParams.isEmpty() && !toFxParams.isEmpty()) {
// gather from-fx parameter pointers
m_model->refreshData(fromXsh);
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
FxChannelGroup* fcg =
dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
if (!fcg) continue;
if (fromFxParams.contains(fcg->getFx()))
gatherParams(fcg, fromFxParams[fcg->getFx()]);
}
// gather to-fx parameter pointers
m_model->refreshData(toXsh);
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
FxChannelGroup* fcg =
dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
if (!fcg) continue;
if (toFxParams.contains(fcg->getFx()))
gatherParams(fcg, toFxParams[fcg->getFx()]);
}
// register parameters to the table
for (auto ffp_itr = fromFxParams.constBegin();
ffp_itr != fromFxParams.constEnd(); ffp_itr++) {
TFx* fromFx = ffp_itr.key();
TFx* toFx = fxTable.value(fromFx);
assert(toFx && toFxParams.contains(toFx));
for (int i = 0; i < ffp_itr.value().size(); i++) {
curveReplaceTable.insert(ffp_itr.value().at(i),
toFxParams.value(toFx).at(i));
}
}
}
// 2. transfer reference information from fromXsh to toXsh by using tables
// QMap<int, int> colIdReplaceTable;
// QMap<TDoubleParam*, TDoubleParam*> curveReplaceTable;
// std::map<std::string, std::string> exprReplaceTable;
QSet<TDoubleParam*> insertedCurves;
for (auto itr = info(fromXsh).begin(); itr != info(fromXsh).end(); itr++) {
TDoubleParam* fromParam = itr.key();
bool ignored = touchInfo(fromParam, fromXsh).ignored();
if (sameXSheet) {
// transfer as-is if the parameter is ignored
if (!ignored) {
// converting the column indices.
QSet<int> convertedColIdSet;
for (auto fromId : itr.value().colRefMap()) {
if (colIdReplaceTable.contains(fromId))
convertedColIdSet.insert(colIdReplaceTable.value(fromId));
// if there is a index not in the replacement table, transfer it
// as-is.
else
convertedColIdSet.insert(fromId);
}
// replacing the info
itr.value().colRefMap() = convertedColIdSet;
}
insertedCurves.insert(fromParam);
}
// if the parameter is in the replacement table
else if (curveReplaceTable.contains(fromParam)) {
// transfer as-is if the parameter is ignored
// converting the column indices.
QSet<int> convertedColIdSet;
for (auto fromId : itr.value().colRefMap()) {
if (ignored) break;
if (colIdReplaceTable.contains(fromId))
convertedColIdSet.insert(colIdReplaceTable.value(fromId));
// if there is a index not in the replacement table, the parameter will
// be ignored
else
ignored = true;
}
// converting the parameter pointers
QSet<TDoubleParam*> convertedParamSet;
for (auto fromRefParam : itr.value().paramRefMap()) {
if (curveReplaceTable.contains(fromRefParam))
convertedParamSet.insert(curveReplaceTable.value(fromRefParam));
// if there is a index not in the replacement table, the parameter will
// be ignored
else
ignored = true;
}
// register the converted list to toXsh
TDoubleParam* toParam = curveReplaceTable.value(fromParam);
if (ignored) {
touchInfo(toParam, toXsh).ignored() = true;
// if the parameter is ignored, transfer the column reference list
// as-is.
touchInfo(toParam, toXsh).colRefMap() = itr.value().colRefMap();
} else
touchInfo(toParam, toXsh).colRefMap() = convertedColIdSet;
touchInfo(toParam, toXsh).paramRefMap() = convertedParamSet;
insertedCurves.insert(toParam);
}
// update parameter names
if (curveReplaceTable.contains(fromParam)) {
TDoubleParam* toParam = curveReplaceTable.value(fromParam);
FunctionTreeModel::Channel* channel = nullptr;
// here m_model should be refreshed using toXsh
for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
channel = findChannel(toParam, m_model->getStageObjectChannel(i));
if (channel) break;
}
if (!channel) {
for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
channel = findChannel(toParam, m_model->getFxChannel(i));
if (channel) break;
}
}
if (channel) {
touchInfo(toParam, toXsh).name() = channel->getLongName();
}
}
}
// refresh m_model with the current xsheet
if (toXsh != TApp::instance()->getCurrentXsheet()->getXsheet())
m_model->refreshData(TApp::instance()->getCurrentXsheet()->getXsheet());
// 3. replace the expression texts
for (auto ic : insertedCurves) replaceExpressionTexts(ic, exprReplaceTable);
}
//----------------------------------------------------------------------------
// open warning popup if there is any parameters which is ignored (i.e. the
// reference is lost and user hasn't touch yet)
bool ExpressionReferenceManager::askIfParamIsIgnoredOnSave(bool saveSubXsheet) {
// return if the preference option is off
bool on =
Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
if (!on) return true;
QSet<TXsheet*> xsheetSet;
TXsheet* parentXsh;
if (saveSubXsheet) // check only inside the current xsheet
parentXsh = TApp::instance()->getCurrentXsheet()->getXsheet();
else // check whole xsheets from the top
parentXsh = TApp::instance()->getCurrentScene()->getScene()->getTopXsheet();
gatherXsheets(parentXsh, xsheetSet);
// gather the ignored parameter names
QStringList ignoredParamNames;
for (auto xsh : xsheetSet) {
bool isParent = (xsh == parentXsh);
for (auto itr = info(xsh).begin(); itr != info(xsh).end(); itr++) {
if (!itr.value().ignored()) continue;
QString paramName = itr.value().name();
if (!isParent) paramName += " " + tr("(In a sub xsheet)");
ignoredParamNames.append(paramName);
}
}
// return if there is not ignored parameters
if (ignoredParamNames.isEmpty()) return true;
// open warning popup
QString warningTxt =
tr("Following parameters may contain broken references in expressions:");
warningTxt += "\n " + ignoredParamNames.join("\n ");
warningTxt += "\n" + tr("Do you want to save the scene anyway ?");
int ret =
DVGui::MsgBox(warningTxt, QObject::tr("Save"), QObject::tr("Cancel"), 0);
if (ret == 0 || ret == 2) return false;
return true;
}