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 }