1 #include "expressionreferencemanager.h"
2 
3 #include "tapp.h"
4 
5 // TnzQt includes
6 #include "toonzqt/dvdialog.h"
7 
8 // TnzLib includes
9 #include "toonz/txsheethandle.h"
10 #include "toonz/tscenehandle.h"
11 #include "toonz/txsheetexpr.h"
12 #include "toonz/doubleparamcmd.h"
13 #include "toonz/preferences.h"
14 #include "toonz/tstageobject.h"
15 #include "toonz/tcolumnfx.h"
16 #include "toonz/txshzeraryfxcolumn.h"
17 #include "toonz/fxdag.h"
18 #include "toonz/tcolumnfxset.h"
19 #include "toonz/toonzscene.h"
20 #include "toonz/txshlevelcolumn.h"
21 #include "toonz/txshcell.h"
22 #include "toonz/txshchildlevel.h"
23 #include "toonz/tstageobjecttree.h"
24 
25 // TnzBase includes
26 #include "tdoubleparam.h"
27 #include "texpression.h"
28 #include "tdoublekeyframe.h"
29 #include "tfx.h"
30 
31 #include "tmsgcore.h"
32 
33 #include <QList>
34 
35 #include <boost/xpressive/xpressive_static.hpp>
36 #include <boost/xpressive/regex_actions.hpp>
37 
38 namespace {
39 // reference : columncommand.cpp
canRemoveFx(const std::set<TFx * > & leaves,TFx * fx)40 bool canRemoveFx(const std::set<TFx*>& leaves, TFx* fx) {
41   bool removeFx = false;
42   for (int i = 0; i < fx->getInputPortCount(); i++) {
43     TFx* inputFx = fx->getInputPort(i)->getFx();
44     if (!inputFx) continue;
45     if (leaves.count(inputFx) > 0) {
46       removeFx = true;
47       continue;
48     }
49     if (!canRemoveFx(leaves, inputFx)) return false;
50     removeFx = true;
51   }
52   return removeFx;
53 }
54 
gatherXsheets(TXsheet * xsheet,QSet<TXsheet * > & ret)55 void gatherXsheets(TXsheet* xsheet, QSet<TXsheet*>& ret) {
56   // return if it is already registered
57   if (ret.contains(xsheet)) return;
58 
59   ret.insert(xsheet);
60 
61   // trace xsheet and recursively find sub-xsheets in it
62   for (int c = 0; c < xsheet->getColumnCount(); c++) {
63     if (xsheet->isColumnEmpty(c)) continue;
64     TXshLevelColumn* levelColumn = xsheet->getColumn(c)->getLevelColumn();
65     if (!levelColumn) continue;
66 
67     int start, end;
68     levelColumn->getRange(start, end);
69     for (int r = start; r <= end; r++) {
70       int r0, r1;
71       if (!levelColumn->getLevelRange(r, r0, r1)) continue;
72 
73       TXshChildLevel* childLevel =
74           levelColumn->getCell(r).m_level->getChildLevel();
75       if (childLevel) {
76         gatherXsheets(childLevel->getXsheet(), ret);
77       }
78 
79       r = r1;
80     }
81   }
82 }
83 
getAllXsheets()84 QSet<TXsheet*> getAllXsheets() {
85   QSet<TXsheet*> ret;
86   TXsheet* topXsheet =
87       TApp::instance()->getCurrentScene()->getScene()->getTopXsheet();
88   gatherXsheets(topXsheet, ret);
89   return ret;
90 }
91 
92 static QList<QList<std::string>> objExprPhrases = {
93     {"table", "tab"},   // Table
94     {"col"},            // Column
95     {"cam", "camera"},  // Camera
96     {"peg", "pegbar"},  // Pegbar
97     {}                  // Spline and others
98 };
99 
getObjTypeIndex(TStageObjectId id)100 int getObjTypeIndex(TStageObjectId id) {
101   if (id.isTable())
102     return 0;
103   else if (id.isColumn())
104     return 1;
105   else if (id.isCamera())
106     return 2;
107   else if (id.isPegbar())
108     return 3;
109   else
110     return 4;
111 }
112 
getPhraseCount(TStageObjectId id)113 int getPhraseCount(TStageObjectId id) {
114   return objExprPhrases[getObjTypeIndex(id)].count();
115 }
116 
getPhrase(TStageObjectId id,int index=0)117 std::string getPhrase(TStageObjectId id, int index = 0) {
118   if (getPhraseCount(id) <= index) index = 0;
119 
120   std::string indexStr =
121       (id.isTable()) ? "" : std::to_string(id.getIndex() + 1);
122   // including period to avoid misunderstanding "col10" as "col1"
123   return objExprPhrases[getObjTypeIndex(id)][index] + indexStr + ".";
124 }
125 
getPhrase(TFx * fx)126 std::string getPhrase(TFx* fx) {
127   QString fxIdStr = QString::fromStdWString(toLower(fx->getFxId()));
128   return "fx." + fxIdStr.toStdString() + ".";
129 }
130 
131 }  // namespace
132 
133 //----------------------------------------------------------------------------
currentMonitor()134 ExpressionReferenceMonitor* ExpressionReferenceManager::currentMonitor() {
135   return TApp::instance()->getCurrentXsheet()->getXsheet()->getExpRefMonitor();
136 }
137 
138 QMap<TDoubleParam*, ExpressionReferenceMonitorInfo>&
info(TXsheet * xsh)139 ExpressionReferenceManager::info(TXsheet* xsh) {
140   if (xsh)
141     return xsh->getExpRefMonitor()->info();
142   else
143     return currentMonitor()->info();
144 }
145 //-----------------------------------------------------------------------------
146 
touchInfo(TDoubleParam * param,TXsheet * xsh)147 ExpressionReferenceMonitorInfo& ExpressionReferenceManager::touchInfo(
148     TDoubleParam* param, TXsheet* xsh) {
149   if (!info(xsh).contains(param)) {
150     ExpressionReferenceMonitorInfo newInfo;
151     info(xsh).insert(param, newInfo);
152     param->addObserver(this);
153   }
154   return info(xsh)[param];
155 }
156 
157 //-----------------------------------------------------------------------------
158 
ExpressionReferenceManager()159 ExpressionReferenceManager::ExpressionReferenceManager()
160     : m_model(new FunctionTreeModel()), m_blockParamChange(false) {}
161 
162 //-----------------------------------------------------------------------------
163 
init()164 void ExpressionReferenceManager::init() {
165   connect(TApp::instance()->getCurrentScene(),
166           SIGNAL(preferenceChanged(const QString&)), this,
167           SLOT(onPreferenceChanged(const QString&)));
168   onPreferenceChanged("modifyExpressionOnMovingReferences");
169 }
170 
171 //-----------------------------------------------------------------------------
172 
onPreferenceChanged(const QString & prefName)173 void ExpressionReferenceManager::onPreferenceChanged(const QString& prefName) {
174   if (prefName != "modifyExpressionOnMovingReferences") return;
175 
176   TXsheetHandle* xshHandle  = TApp::instance()->getCurrentXsheet();
177   TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene();
178   bool on =
179       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
180   if (on) {
181     // when the scene switched, refresh the all list
182     connect(sceneHandle, SIGNAL(sceneSwitched()), this,
183             SLOT(onSceneSwitched()));
184     connect(xshHandle, SIGNAL(xsheetSwitched()), this,
185             SLOT(onXsheetSwitched()));
186     connect(xshHandle, SIGNAL(xsheetChanged()), this, SLOT(onXsheetChanged()));
187     onSceneSwitched();
188   } else {
189     // when the scene switched, refresh the all list
190     disconnect(sceneHandle, SIGNAL(sceneSwitched()), this,
191                SLOT(onSceneSwitched()));
192     disconnect(xshHandle, SIGNAL(xsheetSwitched()), this,
193                SLOT(onXsheetSwitched()));
194     disconnect(xshHandle, SIGNAL(xsheetChanged()), this,
195                SLOT(onXsheetChanged()));
196 
197     // clear all monitor info
198     QSet<TXsheet*> allXsheets = getAllXsheets();
199     for (auto xsh : allXsheets) {
200       for (auto curve : xsh->getExpRefMonitor()->info().keys())
201         curve->removeObserver(this);
202       xsh->getExpRefMonitor()->clearAll();
203       xsh->setObserver(nullptr);
204     }
205   }
206 }
207 
208 //-----------------------------------------------------------------------------
209 
instance()210 ExpressionReferenceManager* ExpressionReferenceManager::instance() {
211   static ExpressionReferenceManager _instance;
212   return &_instance;
213 }
214 
215 //-----------------------------------------------------------------------------
216 
refreshParamsRef(TDoubleParam * curve,TXsheet * xsh)217 bool ExpressionReferenceManager::refreshParamsRef(TDoubleParam* curve,
218                                                   TXsheet* xsh) {
219   QSet<int> colRef;
220   QSet<TDoubleParam*> paramsRef;
221   for (int k = 0; k < curve->getKeyframeCount(); k++) {
222     TDoubleKeyframe keyframe = curve->getKeyframe(k);
223 
224     if (keyframe.m_type != TDoubleKeyframe::Expression &&
225         keyframe.m_type != TDoubleKeyframe::SimilarShape)
226       continue;
227 
228     TExpression expr;
229     expr.setGrammar(curve->getGrammar());
230     expr.setText(keyframe.m_expressionText);
231 
232     QSet<int> tmpColRef;
233     QSet<TDoubleParam*> tmpParamsRef;
234     referenceParams(expr, tmpColRef, tmpParamsRef);
235     colRef += tmpColRef;
236     paramsRef += tmpParamsRef;
237   }
238   // replace the indices
239   bool hasRef = !colRef.isEmpty() || !paramsRef.isEmpty();
240   if (hasRef) {
241     touchInfo(curve, xsh).colRefMap()   = colRef;
242     touchInfo(curve, xsh).paramRefMap() = paramsRef;
243   } else {
244     info(xsh).remove(curve);
245   }
246 
247   return hasRef;
248 }
249 
250 //-----------------------------------------------------------------------------
251 
checkRef(TreeModel::Item * item,TXsheet * xsh)252 void ExpressionReferenceManager::checkRef(TreeModel::Item* item, TXsheet* xsh) {
253   if (FunctionTreeModel::Channel* channel =
254           dynamic_cast<FunctionTreeModel::Channel*>(item)) {
255     TDoubleParam* curve = channel->getParam();
256     bool hasRef         = refreshParamsRef(curve, xsh);
257     if (hasRef) touchInfo(curve, xsh).name() = channel->getLongName();
258   } else
259     for (int i = 0; i < item->getChildCount(); i++)
260       checkRef(item->getChild(i), xsh);
261 }
262 
263 //-----------------------------------------------------------------------------
264 
findChannel(TDoubleParam * param,TreeModel::Item * item)265 FunctionTreeModel::Channel* ExpressionReferenceManager::findChannel(
266     TDoubleParam* param, TreeModel::Item* item) {
267   if (FunctionTreeModel::Channel* channel =
268           dynamic_cast<FunctionTreeModel::Channel*>(item)) {
269     if (channel->getParam() == param) return channel;
270   } else {
271     for (int i = 0; i < item->getChildCount(); i++) {
272       FunctionTreeModel::Channel* ret = findChannel(param, item->getChild(i));
273       if (ret) return ret;
274     }
275   }
276   return nullptr;
277 }
278 
279 //-----------------------------------------------------------------------------
280 
gatherParams(TreeModel::Item * item,QList<TDoubleParam * > & paramSet)281 void ExpressionReferenceManager::gatherParams(TreeModel::Item* item,
282                                               QList<TDoubleParam*>& paramSet) {
283   if (FunctionTreeModel::Channel* channel =
284           dynamic_cast<FunctionTreeModel::Channel*>(item)) {
285     paramSet.append(channel->getParam());
286   } else
287     for (int i = 0; i < item->getChildCount(); i++)
288       gatherParams(item->getChild(i), paramSet);
289 }
290 
291 //-----------------------------------------------------------------------------
292 
onSceneSwitched()293 void ExpressionReferenceManager::onSceneSwitched() {
294   QSet<TXsheet*> allXsheets = getAllXsheets();
295   for (auto xsh : allXsheets) {
296     xsh->setObserver(this);
297 
298     m_model->refreshData(xsh);
299     xsh->getExpRefMonitor()->clearAll();
300 
301     for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
302       checkRef(m_model->getStageObjectChannel(i), xsh);
303     }
304     for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
305       checkRef(m_model->getFxChannel(i), xsh);
306     }
307   }
308   onXsheetSwitched();
309 }
310 
311 //-----------------------------------------------------------------------------
312 
onXsheetSwitched()313 void ExpressionReferenceManager::onXsheetSwitched() {
314   TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
315   xsh->setObserver(this);
316   m_model->refreshData(xsh);
317 }
318 
319 //----------------------------------------------------------------------------
320 
onXsheetChanged()321 void ExpressionReferenceManager::onXsheetChanged() {
322   TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
323   m_model->refreshData(xsh);
324   // remove deleted parameters
325   QList<TDoubleParam*> paramSet;
326   for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
327     gatherParams(m_model->getStageObjectChannel(i), paramSet);
328   }
329   for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
330     gatherParams(m_model->getFxChannel(i), paramSet);
331   }
332 
333   // remove deleted parameter from reference map
334   for (auto itr = info(xsh).begin(); itr != info(xsh).end();) {
335     if (!paramSet.contains(itr.key()))
336       itr = info(xsh).erase(itr);
337     else {
338       // check if the referenced parameters are deleted
339       if (!itr.value().ignored()) {
340         for (auto refParam : itr.value().paramRefMap()) {
341           if (!paramSet.contains(refParam)) {
342             // ignore the parameter if the reference does not exist anymore
343             itr.value().ignored() = true;
344             break;
345           }
346         }
347       }
348       ++itr;
349     }
350   }
351 }
352 
353 //-----------------------------------------------------------------------------
354 
onChange(const TXsheetColumnChange & change)355 void ExpressionReferenceManager::onChange(const TXsheetColumnChange& change) {
356   TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
357   QMap<TStageObjectId, TStageObjectId> idTable;
358 
359   auto setIds = [&](int from, int to) {
360     idTable.insert(TStageObjectId::ColumnId(from),
361                    TStageObjectId::ColumnId(to));
362   };
363 
364   switch (change.m_type) {
365   case TXsheetColumnChange::Insert: {
366     for (int c = xsh->getColumnCount() - 2; c >= change.m_index1; c--) {
367       setIds(c, c + 1);
368     }
369   } break;
370   case TXsheetColumnChange::Remove: {
371     // update ignore info
372     for (auto it = info().begin(); it != info().end(); it++) {
373       if (it.value().colRefMap().contains(change.m_index1))
374         it.value().ignored() = true;
375     }
376     for (int c = change.m_index1; c < xsh->getColumnCount(); c++) {
377       setIds(c + 1, c);
378     }
379   } break;
380   case TXsheetColumnChange::Move: {
381     if (change.m_index1 < change.m_index2) {
382       setIds(change.m_index1, change.m_index2);
383       for (int c = change.m_index1 + 1; c <= change.m_index2; c++) {
384         setIds(c, c - 1);
385       }
386     } else {
387       setIds(change.m_index1, change.m_index2);
388       for (int c = change.m_index2; c < change.m_index1; c++) {
389         setIds(c, c + 1);
390       }
391     }
392   } break;
393   }
394 
395   // use empty map since the fxs does not transfer
396   QMap<TFx*, TFx*> fxTable;
397   transferReference(xsh, xsh, idTable, fxTable);
398 }
399 
onFxAdded(const std::vector<TFx * > & fxs)400 void ExpressionReferenceManager::onFxAdded(const std::vector<TFx*>& fxs) {
401   for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
402     FxChannelGroup* fcg =
403         dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
404     if (fcg && fxs.end() != std::find(fxs.begin(), fxs.end(), fcg->getFx()))
405       checkRef(fcg);
406   }
407 }
408 
onStageObjectAdded(const TStageObjectId objId)409 void ExpressionReferenceManager::onStageObjectAdded(
410     const TStageObjectId objId) {
411   for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
412     StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
413         m_model->getStageObjectChannel(i));
414     if (socg && objId == socg->getStageObject()->getId()) checkRef(socg);
415   }
416 }
417 
isIgnored(TDoubleParam * param)418 bool ExpressionReferenceManager::isIgnored(TDoubleParam* param) {
419   return touchInfo(param).ignored();
420 }
421 
422 //-----------------------------------------------------------------------------
423 // TParamObserver implementation
onChange(const TParamChange & change)424 void ExpressionReferenceManager::onChange(const TParamChange& change) {
425   // do nothing if the change is due to this manager itself
426   if (m_blockParamChange) return;
427   // do nothing if keyframe does not change or while dragging
428   if (!change.m_keyframeChanged || change.m_dragging) return;
429   TDoubleParam* curve = dynamic_cast<TDoubleParam*>(change.m_param);
430   if (!curve) return;
431   bool hasRef = refreshParamsRef(curve, nullptr);
432   if (hasRef) {
433     FunctionTreeModel::Channel* channel = nullptr;
434     for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
435       channel = findChannel(curve, m_model->getStageObjectChannel(i));
436       if (channel) break;
437     }
438     if (!channel) {
439       for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
440         channel = findChannel(curve, m_model->getFxChannel(i));
441         if (channel) break;
442       }
443     }
444     if (channel) {
445       touchInfo(curve).name() = channel->getLongName();
446     }
447 
448     if (touchInfo(curve).ignored()) {
449       DVGui::info(tr("Expression monitoring restarted: \"%1\"")
450                       .arg(touchInfo(curve).name()));
451       touchInfo(curve).ignored() = false;
452     }
453   }
454 }
455 
456 //-----------------------------------------------------------------------------
457 
replaceExpressionTexts(TDoubleParam * curve,const std::map<std::string,std::string> replaceMap,TXsheet * xsh)458 void ExpressionReferenceManager::replaceExpressionTexts(
459     TDoubleParam* curve, const std::map<std::string, std::string> replaceMap,
460     TXsheet* xsh) {
461   if (touchInfo(curve, xsh).ignored() || replaceMap.empty()) {
462     for (int kIndex = 0; kIndex < curve->getKeyframeCount(); kIndex++) {
463       TDoubleKeyframe keyframe = curve->getKeyframe(kIndex);
464       if (keyframe.m_type != TDoubleKeyframe::Expression &&
465           keyframe.m_type != TDoubleKeyframe::SimilarShape)
466         continue;
467 
468       // check circular reference
469       TExpression expr;
470       expr.setGrammar(curve->getGrammar());
471       expr.setText(keyframe.m_expressionText);
472       // put "?" marks on both ends of the expression text when the circular
473       // reference is detected in order to avoid crash
474       if (dependsOn(expr, curve))
475         keyframe.m_expressionText = "?" + keyframe.m_expressionText + "?";
476 
477       m_blockParamChange = true;
478       KeyframeSetter setter(curve, kIndex, false);
479       if (keyframe.m_type == TDoubleKeyframe::Expression)
480         setter.setExpression(keyframe.m_expressionText);
481       else  // SimilarShape case
482         setter.setSimilarShape(keyframe.m_expressionText,
483                                keyframe.m_similarShapeOffset);
484       m_blockParamChange = false;
485     }
486 
487     return;
488   }
489 
490   boost::xpressive::local<std::string const*> pstr;
491   const boost::xpressive::sregex rx =
492       (boost::xpressive::a1 = replaceMap)[pstr = &boost::xpressive::a1];
493 
494   for (int kIndex = 0; kIndex < curve->getKeyframeCount(); kIndex++) {
495     TDoubleKeyframe keyframe = curve->getKeyframe(kIndex);
496 
497     if (keyframe.m_type != TDoubleKeyframe::Expression &&
498         keyframe.m_type != TDoubleKeyframe::SimilarShape)
499       continue;
500 
501     // replace expression
502     QString expr       = QString::fromStdString(keyframe.m_expressionText);
503     QStringList list   = expr.split('"');
504     bool isStringToken = false;
505     for (QString& partialExp : list) {
506       if (isStringToken) continue;
507       isStringToken = !isStringToken;
508 
509       std::string partialExpStr = partialExp.toStdString();
510       std::string replacedStr =
511           boost::xpressive::regex_replace(partialExpStr, rx, *pstr);
512       partialExp = QString::fromStdString(replacedStr);
513     }
514 
515     QString newExpr = list.join('"');
516 
517     m_blockParamChange = true;
518     KeyframeSetter setter(curve, kIndex, false);
519     if (keyframe.m_type == TDoubleKeyframe::Expression)
520       setter.setExpression(newExpr.toStdString());
521     else  // SimilarShape case
522       setter.setSimilarShape(newExpr.toStdString(),
523                              keyframe.m_similarShapeOffset);
524     m_blockParamChange = false;
525 
526     if (newExpr != expr) {
527       DVGui::info(tr("Expression modified: \"%1\" key at frame %2, %3 -> %4")
528                       .arg(touchInfo(curve, xsh).name())
529                       .arg(keyframe.m_frame + 1)
530                       .arg(expr)
531                       .arg(newExpr));
532     }
533   }
534 }
535 
536 //-----------------------------------------------------------------------------
doCheckReferenceDeletion(const QSet<int> & columnIdsToBeDeleted,const QSet<TFx * > & fxsToBeDeleted,const QList<TStageObjectId> & objectIdsToBeDeleted,const QList<TStageObjectId> & objIdsToBeDuplicated,bool checkInvert)537 bool ExpressionReferenceManager::doCheckReferenceDeletion(
538     const QSet<int>& columnIdsToBeDeleted, const QSet<TFx*>& fxsToBeDeleted,
539     const QList<TStageObjectId>& objectIdsToBeDeleted,
540     const QList<TStageObjectId>& objIdsToBeDuplicated, bool checkInvert) {
541   QList<TDoubleParam*> paramsToBeDeleted;
542   QList<TDoubleParam*> invParamsToBeDeleted;
543   // gather Fx parameters to be deleted
544   for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
545     FxChannelGroup* fcg =
546         dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
547     if (!fcg) continue;
548     if (fxsToBeDeleted.contains(fcg->getFx()))
549       gatherParams(fcg, paramsToBeDeleted);
550     else if (checkInvert)
551       gatherParams(fcg, invParamsToBeDeleted);
552   }
553   // gather stage objects parameters to be deleted
554   for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
555     StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
556         m_model->getStageObjectChannel(i));
557     if (!socg) continue;
558     TStageObjectId id = socg->getStageObject()->getId();
559     if (objectIdsToBeDeleted.contains(id))
560       gatherParams(socg, paramsToBeDeleted);
561     // objects to be duplicated in the sub xsheet will not lose referenced from
562     // either xsheet
563     else if (checkInvert && !objIdsToBeDuplicated.contains(id))
564       gatherParams(socg, invParamsToBeDeleted);
565   }
566 
567   // gather parameters which refers to the parameters to be deleted
568   QSet<TDoubleParam*> cautionParams;
569   QSet<TDoubleParam*> invCautionParams;
570 
571   for (auto itr = info().begin(); itr != info().end(); itr++) {
572     // find params containing columnId to be deleted
573     for (auto refColId : itr.value().colRefMap()) {
574       if (columnIdsToBeDeleted.contains(refColId))
575         cautionParams.insert(itr.key());
576       else if (checkInvert)
577         invCautionParams.insert(itr.key());
578     }
579     // find params containing fx/stage params to be deleted as well
580     for (auto refParam : itr.value().paramRefMap()) {
581       if (paramsToBeDeleted.contains(refParam)) cautionParams.insert(itr.key());
582       if (checkInvert && invParamsToBeDeleted.contains(refParam))
583         invCautionParams.insert(itr.key());
584     }
585   }
586 
587   // remove parameters from the list which itself will be deleted
588   for (auto it = cautionParams.begin(); it != cautionParams.end();)
589     if (paramsToBeDeleted.contains(*it))
590       it = cautionParams.erase(it);
591     else
592       ++it;
593   for (auto it = invCautionParams.begin(); it != invCautionParams.end();)
594     if (invParamsToBeDeleted.contains(*it))
595       it = invCautionParams.erase(it);
596     else
597       ++it;
598 
599   // return true if there is no parameters which will lose references
600   if (cautionParams.isEmpty() && invCautionParams.isEmpty()) return true;
601 
602   // open warning popup
603   QString warningTxt =
604       tr("Following parameters will lose reference in expressions:");
605   for (auto param : cautionParams) {
606     warningTxt += "\n  " + touchInfo(param).name();
607   }
608   for (auto param : invCautionParams) {
609     warningTxt += "\n  " + touchInfo(param).name() + "  " +
610                   tr("(To be in the sub xsheet)");
611   }
612   warningTxt += "\n" + tr("Do you want to continue the operation anyway ?");
613 
614   int ret = DVGui::MsgBox(warningTxt, QObject::tr("Continue"),
615                           QObject::tr("Cancel"), 0);
616   if (ret == 0 || ret == 2) return false;
617 
618   return true;
619 }
620 
621 //-----------------------------------------------------------------------------
622 // check on deleting columns
checkReferenceDeletion(const QSet<int> & columnIdsToBeDeleted,const QSet<TFx * > & fxsToBeDeleted,const QList<TStageObjectId> & objIdsToBeDuplicated,bool checkInvert)623 bool ExpressionReferenceManager::checkReferenceDeletion(
624     const QSet<int>& columnIdsToBeDeleted, const QSet<TFx*>& fxsToBeDeleted,
625     const QList<TStageObjectId>& objIdsToBeDuplicated, bool checkInvert) {
626   bool on =
627       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
628   if (!on) return true;
629   QList<TStageObjectId> objectIdsToBeDeleted;
630   for (auto colId : columnIdsToBeDeleted)
631     objectIdsToBeDeleted.append(TStageObjectId::ColumnId(colId));
632 
633   return doCheckReferenceDeletion(columnIdsToBeDeleted, fxsToBeDeleted,
634                                   objectIdsToBeDeleted, objIdsToBeDuplicated,
635                                   checkInvert);
636 }
637 
638 //-----------------------------------------------------------------------------
639 // check on deleting stage objects
checkReferenceDeletion(const QList<TStageObjectId> & objectIdsToBeDeleted)640 bool ExpressionReferenceManager::checkReferenceDeletion(
641     const QList<TStageObjectId>& objectIdsToBeDeleted) {
642   bool on =
643       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
644   if (!on) return true;
645   QSet<int> columnIdsToBeDeleted;
646   QSet<TFx*> fxsToBeDeleted;
647 
648   TApp* app    = TApp::instance();
649   TXsheet* xsh = app->getCurrentXsheet()->getXsheet();
650   std::set<TFx*> leaves;
651   // fx references should be checked when deleting columns
652   for (const auto& objId : objectIdsToBeDeleted) {
653     if (objId.isColumn()) {
654       int index = objId.getIndex();
655       if (index < 0) continue;
656       TXshColumn* column = xsh->getColumn(index);
657       if (!column) continue;
658       columnIdsToBeDeleted.insert(index);
659       TFx* fx = column->getFx();
660       if (fx) {
661         leaves.insert(fx);
662         TZeraryColumnFx* zcfx = dynamic_cast<TZeraryColumnFx*>(fx);
663         if (zcfx) fxsToBeDeleted.insert(zcfx->getZeraryFx());
664       }
665     }
666   }
667   // store fx to be deleted along with columns
668   TFxSet* fxSet = xsh->getFxDag()->getInternalFxs();
669   for (int i = 0; i < fxSet->getFxCount(); i++) {
670     TFx* fx = fxSet->getFx(i);
671     if (canRemoveFx(leaves, fx)) fxsToBeDeleted.insert(fx);
672   }
673   QList<TStageObjectId> dummy;
674 
675   return doCheckReferenceDeletion(columnIdsToBeDeleted, fxsToBeDeleted,
676                                   objectIdsToBeDeleted, dummy);
677 }
678 
679 //-----------------------------------------------------------------------------
680 // check on exploding sub xsheet.
681 // - If removeColumn is true, it means that the sub xsheet column in the parent
682 // xsheet will be deleted.
683 // - If columnsOnly is true, it means that all references to the objects other
684 // than columns in the sub xsheet will be lost.
685 // - If columnsOnly is false, it means that references to camera not connected
686 // to the table node in the sub xsheet will be lost.
687 // - Open warning popup if there is any expression which will lose reference
688 // after the operation.
689 
checkExplode(TXsheet * childXsh,int index,bool removeColumn,bool columnsOnly)690 bool ExpressionReferenceManager::checkExplode(TXsheet* childXsh, int index,
691                                               bool removeColumn,
692                                               bool columnsOnly) {
693   // return if the preference option is off
694   bool on =
695       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
696   if (!on) return true;
697 
698   QSet<TDoubleParam*> mainCautionParams, subCautionParams;
699   if (removeColumn) {
700     // find params referring to the sub xsheet column to be exploded and removed
701     for (auto itr = info().begin(); itr != info().end(); itr++) {
702       if (itr.value().colRefMap().contains(index))
703         mainCautionParams.insert(itr.key());
704     }
705   }
706 
707   m_model->refreshData(childXsh);
708   // find params referring to the stage params to be deleted
709   QList<TDoubleParam*> stageParamsToBeDeleted;
710   TStageObject* table = childXsh->getStageObject(TStageObjectId::TableId);
711   for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
712     StageObjectChannelGroup* socg = dynamic_cast<StageObjectChannelGroup*>(
713         m_model->getStageObjectChannel(i));
714     if (!socg) continue;
715     TStageObjectId id = socg->getStageObject()->getId();
716     if ((columnsOnly && !id.isColumn()) ||
717         (!columnsOnly && !socg->getStageObject()->isAncestor(table)))
718       gatherParams(socg, stageParamsToBeDeleted);
719   }
720   for (auto itr = info(childXsh).begin(); itr != info(childXsh).end(); itr++) {
721     for (auto refParam : itr.value().paramRefMap()) {
722       if (stageParamsToBeDeleted.contains(refParam)) {
723         subCautionParams.insert(itr.key());
724         break;
725       }
726     }
727   }
728 
729   // remove parameters from the list which itself will be deleted
730   for (auto it = subCautionParams.begin(); it != subCautionParams.end();)
731     if (stageParamsToBeDeleted.contains(*it))
732       it = subCautionParams.erase(it);
733     else
734       ++it;
735 
736   TXsheet* currentXsh = TApp::instance()->getCurrentXsheet()->getXsheet();
737   m_model->refreshData(currentXsh);
738 
739   // return true if there is no parameters which will lose references
740   if (mainCautionParams.isEmpty() && subCautionParams.isEmpty()) return true;
741 
742   // open warning popup
743   QString warningTxt =
744       tr("Following parameters will lose reference in expressions:");
745   for (auto param : mainCautionParams) {
746     warningTxt +=
747         "\n  " + touchInfo(param).name() + "  " + tr("(In the current xsheet)");
748   }
749   for (auto param : subCautionParams) {
750     warningTxt += "\n  " + touchInfo(param, childXsh).name() + "  " +
751                   tr("(To be brought from the subxsheet)");
752   }
753   warningTxt += tr("\nDo you want to explode anyway ?");
754 
755   int ret = DVGui::MsgBox(warningTxt, QObject::tr("Explode"),
756                           QObject::tr("Cancel"), 0);
757   if (ret == 0 || ret == 2) return false;
758 
759   return true;
760 }
761 
762 //----------------------------------------------------------------------------
763 
transferReference(TXsheet * fromXsh,TXsheet * toXsh,const QMap<TStageObjectId,TStageObjectId> & idTable,const QMap<TFx *,TFx * > & fxTable)764 void ExpressionReferenceManager::transferReference(
765     TXsheet* fromXsh, TXsheet* toXsh,
766     const QMap<TStageObjectId, TStageObjectId>& idTable,
767     const QMap<TFx*, TFx*>& fxTable) {
768   // return if the preference option is off
769   bool on =
770       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
771   if (!on) return;
772 
773   // 1. create 3 tables for replacing; column indices, parameter pointers, and
774   // expression texts. Note that moving columns in the same xsheet does not need
775   // to replace the paramter pointers since they are swapped along with columns.
776   QMap<int, int> colIdReplaceTable;
777   QMap<TDoubleParam*, TDoubleParam*> curveReplaceTable;
778   std::map<std::string, std::string> exprReplaceTable;
779 
780   bool sameXSheet = (fromXsh == toXsh);
781 
782   // First, check the stage objects
783   for (auto obj_itr = idTable.constBegin(); obj_itr != idTable.constEnd();
784        obj_itr++) {
785     TStageObjectId fromId = obj_itr.key();
786     TStageObjectId toId   = obj_itr.value();
787 
788     // register column indices replacement table ( register even if fromId and
789     // toId are identical )
790     if (fromId.isColumn() && toId.isColumn())
791       colIdReplaceTable.insert(fromId.getIndex(), toId.getIndex());
792     // register expression texts replacement table ( register only if the
793     // phrases will be changed )
794     if (fromId != toId) {
795       for (int ph = 0; ph < getPhraseCount(fromId); ph++)
796         exprReplaceTable[getPhrase(fromId, ph)] = getPhrase(toId, ph);
797     }
798     if (sameXSheet) {
799       // the paramter pointers are already swapped when moving columns in the
800       // same xsheet curveReplaceTable will be used just for parameter list to
801       // be modified
802       TStageObject* toObj =
803           toXsh->getStageObjectTree()->getStageObject(toId, false);
804       assert(toObj);
805       if (toObj) {
806         for (int c = 0; c < TStageObject::T_ChannelCount; c++) {
807           TDoubleParam* to_p = toObj->getParam((TStageObject::Channel)c);
808           curveReplaceTable.insert(to_p, to_p);
809         }
810       }
811     } else {  // for transferring objects over xsheets (i.e. collapse and
812               // explode)
813       // register to the parameter pointer replacement table
814       TStageObject* fromObj =
815           fromXsh->getStageObjectTree()->getStageObject(fromId, false);
816       TStageObject* toObj =
817           toXsh->getStageObjectTree()->getStageObject(toId, false);
818       assert(fromObj && toObj);
819       if (fromObj && toObj) {
820         for (int c = 0; c < TStageObject::T_ChannelCount; c++) {
821           TDoubleParam* from_p = fromObj->getParam((TStageObject::Channel)c);
822           TDoubleParam* to_p   = toObj->getParam((TStageObject::Channel)c);
823           curveReplaceTable.insert(from_p, to_p);
824         }
825       }
826     }
827   }
828 
829   // Secondly, check the Fxs
830   QMap<TFx*, QList<TDoubleParam*>> fromFxParams, toFxParams;
831   for (auto fx_itr = fxTable.constBegin(); fx_itr != fxTable.constEnd();
832        fx_itr++) {
833     TFx* fromFx = fx_itr.key();
834     TFx* toFx   = fx_itr.value();
835     // skip the case that the Xsheet node is converted to the OverFx when
836     // exploding
837     if (fromFx->getFxType() == toFx->getFxType()) {
838       fromFxParams.insert(fromFx, QList<TDoubleParam*>());
839       toFxParams.insert(toFx, QList<TDoubleParam*>());
840       // register expression texts replacement table
841       if (fromFx->getFxId() != toFx->getFxId())
842         exprReplaceTable[getPhrase(fromFx)] = getPhrase(toFx);
843     }
844   }
845   if (!fromFxParams.isEmpty() && !toFxParams.isEmpty()) {
846     // gather from-fx parameter pointers
847     m_model->refreshData(fromXsh);
848     for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
849       FxChannelGroup* fcg =
850           dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
851       if (!fcg) continue;
852       if (fromFxParams.contains(fcg->getFx()))
853         gatherParams(fcg, fromFxParams[fcg->getFx()]);
854     }
855     // gather to-fx parameter pointers
856     m_model->refreshData(toXsh);
857     for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
858       FxChannelGroup* fcg =
859           dynamic_cast<FxChannelGroup*>(m_model->getFxChannel(i));
860       if (!fcg) continue;
861       if (toFxParams.contains(fcg->getFx()))
862         gatherParams(fcg, toFxParams[fcg->getFx()]);
863     }
864     // register parameters to the table
865     for (auto ffp_itr = fromFxParams.constBegin();
866          ffp_itr != fromFxParams.constEnd(); ffp_itr++) {
867       TFx* fromFx = ffp_itr.key();
868       TFx* toFx   = fxTable.value(fromFx);
869       assert(toFx && toFxParams.contains(toFx));
870       for (int i = 0; i < ffp_itr.value().size(); i++) {
871         curveReplaceTable.insert(ffp_itr.value().at(i),
872                                  toFxParams.value(toFx).at(i));
873       }
874     }
875   }
876 
877   // 2. transfer reference information from fromXsh to toXsh by using tables
878   // QMap<int, int> colIdReplaceTable;
879   // QMap<TDoubleParam*, TDoubleParam*> curveReplaceTable;
880   // std::map<std::string, std::string> exprReplaceTable;
881   QSet<TDoubleParam*> insertedCurves;
882   for (auto itr = info(fromXsh).begin(); itr != info(fromXsh).end(); itr++) {
883     TDoubleParam* fromParam = itr.key();
884     bool ignored            = touchInfo(fromParam, fromXsh).ignored();
885     if (sameXSheet) {
886       // transfer as-is if the parameter is ignored
887       if (!ignored) {
888         // converting the column indices.
889         QSet<int> convertedColIdSet;
890         for (auto fromId : itr.value().colRefMap()) {
891           if (colIdReplaceTable.contains(fromId))
892             convertedColIdSet.insert(colIdReplaceTable.value(fromId));
893           // if there is a index not in the replacement table, transfer it
894           // as-is.
895           else
896             convertedColIdSet.insert(fromId);
897         }
898         // replacing the info
899         itr.value().colRefMap() = convertedColIdSet;
900       }
901       insertedCurves.insert(fromParam);
902     }
903     // if the parameter is in the replacement table
904     else if (curveReplaceTable.contains(fromParam)) {
905       // transfer as-is if the parameter is ignored
906       // converting the column indices.
907       QSet<int> convertedColIdSet;
908       for (auto fromId : itr.value().colRefMap()) {
909         if (ignored) break;
910         if (colIdReplaceTable.contains(fromId))
911           convertedColIdSet.insert(colIdReplaceTable.value(fromId));
912         // if there is a index not in the replacement table, the parameter will
913         // be ignored
914         else
915           ignored = true;
916       }
917 
918       // converting the parameter pointers
919       QSet<TDoubleParam*> convertedParamSet;
920       for (auto fromRefParam : itr.value().paramRefMap()) {
921         if (curveReplaceTable.contains(fromRefParam))
922           convertedParamSet.insert(curveReplaceTable.value(fromRefParam));
923         // if there is a index not in the replacement table, the parameter will
924         // be ignored
925         else
926           ignored = true;
927       }
928 
929       // register the converted list to toXsh
930       TDoubleParam* toParam = curveReplaceTable.value(fromParam);
931       if (ignored) {
932         touchInfo(toParam, toXsh).ignored() = true;
933         // if the parameter is ignored, transfer the column reference list
934         // as-is.
935         touchInfo(toParam, toXsh).colRefMap() = itr.value().colRefMap();
936       } else
937         touchInfo(toParam, toXsh).colRefMap() = convertedColIdSet;
938 
939       touchInfo(toParam, toXsh).paramRefMap() = convertedParamSet;
940 
941       insertedCurves.insert(toParam);
942     }
943 
944     // update parameter names
945     if (curveReplaceTable.contains(fromParam)) {
946       TDoubleParam* toParam               = curveReplaceTable.value(fromParam);
947       FunctionTreeModel::Channel* channel = nullptr;
948       // here m_model should be refreshed using toXsh
949       for (int i = 0; i < m_model->getStageObjectsChannelCount(); i++) {
950         channel = findChannel(toParam, m_model->getStageObjectChannel(i));
951         if (channel) break;
952       }
953       if (!channel) {
954         for (int i = 0; i < m_model->getFxsChannelCount(); i++) {
955           channel = findChannel(toParam, m_model->getFxChannel(i));
956           if (channel) break;
957         }
958       }
959       if (channel) {
960         touchInfo(toParam, toXsh).name() = channel->getLongName();
961       }
962     }
963   }
964 
965   // refresh m_model with the current xsheet
966   if (toXsh != TApp::instance()->getCurrentXsheet()->getXsheet())
967     m_model->refreshData(TApp::instance()->getCurrentXsheet()->getXsheet());
968 
969   // 3. replace the expression texts
970   for (auto ic : insertedCurves) replaceExpressionTexts(ic, exprReplaceTable);
971 }
972 
973 //----------------------------------------------------------------------------
974 // open warning popup if there is any paramters which is ignored (i.e. the
975 // reference is lost and user hasn't touch yet)
askIfParamIsIgnoredOnSave(bool saveSubXsheet)976 bool ExpressionReferenceManager::askIfParamIsIgnoredOnSave(bool saveSubXsheet) {
977   // return if the preference option is off
978   bool on =
979       Preferences::instance()->isModifyExpressionOnMovingReferencesEnabled();
980   if (!on) return true;
981   QSet<TXsheet*> xsheetSet;
982   TXsheet* parentXsh;
983   if (saveSubXsheet)  // check only inside the current xsheet
984     parentXsh = TApp::instance()->getCurrentXsheet()->getXsheet();
985   else  // check whole xsheets from the top
986     parentXsh = TApp::instance()->getCurrentScene()->getScene()->getTopXsheet();
987 
988   gatherXsheets(parentXsh, xsheetSet);
989 
990   // gather the ignored parameter names
991   QStringList ignoredParamNames;
992   for (auto xsh : xsheetSet) {
993     bool isParent = (xsh == parentXsh);
994     for (auto itr = info(xsh).begin(); itr != info(xsh).end(); itr++) {
995       if (!itr.value().ignored()) continue;
996       QString paramName = itr.value().name();
997       if (!isParent) paramName += "  " + tr("(In a sub xsheet)");
998       ignoredParamNames.append(paramName);
999     }
1000   }
1001 
1002   // return if there is not ignored parameters
1003   if (ignoredParamNames.isEmpty()) return true;
1004 
1005   // open warning popup
1006   QString warningTxt =
1007       tr("Following parameters may contain broken references in expressions:");
1008   warningTxt += "\n  " + ignoredParamNames.join("\n  ");
1009   warningTxt += "\n" + tr("Do you want to save the scene anyway ?");
1010 
1011   int ret =
1012       DVGui::MsgBox(warningTxt, QObject::tr("Save"), QObject::tr("Cancel"), 0);
1013   if (ret == 0 || ret == 2) return false;
1014 
1015   return true;
1016 }