1 
2 
3 #include "toonz/fxcommand.h"
4 
5 // TnzLib includes
6 #include "toonz/txsheet.h"
7 #include "toonz/tcolumnfx.h"
8 #include "toonz/fxdag.h"
9 #include "toonz/tcolumnfxset.h"
10 #include "toonz/txshzeraryfxcolumn.h"
11 #include "toonz/tstageobjecttree.h"
12 #include "toonz/txshlevelcolumn.h"
13 #include "toonz/txshpalettecolumn.h"
14 #include "toonz/toonzscene.h"
15 #include "toonz/txsheethandle.h"
16 #include "toonz/tfxhandle.h"
17 #include "toonz/tcolumnhandle.h"
18 #include "toonz/tscenehandle.h"
19 #include "historytypes.h"
20 
21 // TnzBase includes
22 #include "tparamcontainer.h"
23 #include "tparamset.h"
24 #include "tfxattributes.h"
25 #include "tmacrofx.h"
26 #include "tpassivecachemanager.h"
27 
28 // TnzCore includes
29 #include "tundo.h"
30 #include "tconst.h"
31 
32 // Qt includes
33 #include <QMap>
34 
35 // tcg includes
36 #include "tcg/tcg_macros.h"
37 #include "tcg/tcg_base.h"
38 
39 #include <memory>
40 
41 /*
42   Toonz currently has THREE different APIs to deal with scene objects commands:
43 
44     1. From the xsheet, see columncommand.cpp
45     2. From the stage schematic
46     3. From the fx schematic
47 
48   *This* is the one version that should 'rule them all' - we still have to
49   unify them, though.
50 
51 TODO:
52 
53   - Associated Stage Object copies when copying columns
54   - Stage schematic currently ignored (eg delete columns undo)
55   - Double-check macro fxs behavior
56   - Double-check group behavior
57   - Enforce dynamic link groups consistency
58 */
59 
60 //**********************************************************************
61 //    Local Namespace  stuff
62 //**********************************************************************
63 
64 namespace {
65 
66 //======================================================
67 
getActualIn(TFx * fx)68 inline TFx *getActualIn(TFx *fx) {
69   TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx);
70   return zcfx ? (assert(zcfx->getZeraryFx()), zcfx->getZeraryFx()) : fx;
71 }
72 
73 //------------------------------------------------------
74 
getActualOut(TFx * fx)75 inline TFx *getActualOut(TFx *fx) {
76   TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx);
77   return (zfx && zfx->getColumnFx()) ? zfx->getColumnFx() : fx;
78 }
79 
80 //------------------------------------------------------
81 
inputPortIndex(TFx * fx,TFxPort * port)82 inline int inputPortIndex(TFx *fx, TFxPort *port) {
83   int p, pCount = fx->getInputPortCount();
84   for (p = 0; p != pCount; ++p)
85     if (fx->getInputPort(p) == port) break;
86   return p;
87 }
88 
89 //------------------------------------------------------
90 
91 /*!
92   Returns whether the specified fx is internal to a macro fx. Fxs
93   inside a macro should not be affected by most editing commands - the
94   macro is required to be exploded first.
95 */
isInsideAMacroFx(TFx * fx,TXsheet * xsh)96 bool isInsideAMacroFx(TFx *fx, TXsheet *xsh) {
97   if (!fx) return false;
98 
99   TColumnFx *cfx = dynamic_cast<TColumnFx *>(fx);
100   TXsheetFx *xfx = dynamic_cast<TXsheetFx *>(fx);
101   TOutputFx *ofx = dynamic_cast<TOutputFx *>(fx);
102 
103   return !cfx && !xfx && !ofx &&
104          !(xsh->getFxDag()->getInternalFxs()->containsFx(fx));
105 }
106 
107 //------------------------------------------------------
108 
109 template <typename ParamCont>
setParamsToCurrentScene(TXsheet * xsh,const ParamCont * cont)110 void setParamsToCurrentScene(TXsheet *xsh, const ParamCont *cont) {
111   for (int p = 0; p != cont->getParamCount(); ++p) {
112     TParam &param = *cont->getParam(p);
113 
114     if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(&param))
115       xsh->getStageObjectTree()->setGrammar(dp);
116     else if (TParamSet *paramSet = dynamic_cast<TParamSet *>(&param))
117       setParamsToCurrentScene(xsh, paramSet);
118   }
119 }
120 
121 //------------------------------------------------------
122 
setFxParamToCurrentScene(TFx * fx,TXsheet * xsh)123 inline void setFxParamToCurrentScene(TFx *fx, TXsheet *xsh) {
124   setParamsToCurrentScene(xsh, fx->getParams());
125 }
126 
127 //------------------------------------------------------
128 
initializeFx(TXsheet * xsh,TFx * fx)129 void initializeFx(TXsheet *xsh, TFx *fx) {
130   if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
131     fx = zcfx->getZeraryFx();
132 
133   xsh->getFxDag()->assignUniqueId(fx);
134   setFxParamToCurrentScene(fx, xsh);
135 }
136 
137 //------------------------------------------------------
138 
showFx(TXsheet * xsh,TFx * fx)139 void showFx(TXsheet *xsh, TFx *fx) {
140   fx->getAttributes()->setIsOpened(xsh->getFxDag()->getDagGridDimension() == 0);
141 
142   if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
143     fx = zcfx->getZeraryFx();
144   fx->getAttributes()->passiveCacheDataIdx() = -1;
145 }
146 
147 //------------------------------------------------------
148 
hideFx(TXsheet * xsh,TFx * fx)149 void hideFx(TXsheet *xsh, TFx *fx) {
150   if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
151     fx = zcfx->getZeraryFx();
152   TPassiveCacheManager::instance()->disableCache(fx);
153 }
154 
155 //------------------------------------------------------
156 
addFxToCurrentScene(TFx * fx,TXsheet * xsh,bool isNewFx=true)157 void addFxToCurrentScene(TFx *fx, TXsheet *xsh, bool isNewFx = true) {
158   if (isNewFx) initializeFx(xsh, fx);
159 
160   xsh->getFxDag()->getInternalFxs()->addFx(fx);
161 
162   showFx(xsh, fx);
163 }
164 
165 //------------------------------------------------------
166 
removeFxFromCurrentScene(TFx * fx,TXsheet * xsh)167 void removeFxFromCurrentScene(TFx *fx, TXsheet *xsh) {
168   xsh->getFxDag()->getInternalFxs()->removeFx(fx);
169   xsh->getFxDag()->getTerminalFxs()->removeFx(fx);
170 
171   hideFx(xsh, fx);
172 }
173 
174 }  // namespace
175 
176 //**********************************************************************
177 //    Filter Functors  definition
178 //**********************************************************************
179 
180 namespace {
181 
182 struct FilterInsideAMacro {
183   TXsheet *m_xsh;
operator ()__anon13f77d830211::FilterInsideAMacro184   inline bool operator()(const TFxP &fx) {
185     return ::isInsideAMacroFx(fx.getPointer(), m_xsh);
186   }
187 
operator ()__anon13f77d830211::FilterInsideAMacro188   inline bool operator()(const TFxCommand::Link &link) {
189     return ::isInsideAMacroFx(link.m_inputFx.getPointer(), m_xsh) ||
190            ::isInsideAMacroFx(link.m_outputFx.getPointer(), m_xsh);
191   }
192 };
193 
194 struct FilterNonTerminalFxs {
195   TXsheet *xsh;
operator ()__anon13f77d830211::FilterNonTerminalFxs196   inline bool operator()(const TFxP &fx) {
197     return !xsh->getFxDag()->getTerminalFxs()->containsFx(fx.getPointer());
198   }
199 };
200 
201 struct FilterTerminalFxs {
202   TXsheet *xsh;
operator ()__anon13f77d830211::FilterTerminalFxs203   inline bool operator()(const TFxP &fx) {
204     return xsh->getFxDag()->getTerminalFxs()->containsFx(fx.getPointer());
205   }
206 };
207 
208 struct FilterColumnFxs {
operator ()__anon13f77d830211::FilterColumnFxs209   inline bool operator()(const TFxP &fx) {
210     return dynamic_cast<TLevelColumnFx *>(fx.getPointer());
211   }
212 };
213 
214 }  // namespace
215 
216 //**********************************************************************
217 //    CloneFxFunctor  definition
218 //**********************************************************************
219 
220 namespace {
221 
222 struct CloneFxFunctor {
223   TFxP m_src;
224   bool m_ownsSrc;
operator ()__anon13f77d830311::CloneFxFunctor225   TFx *operator()() {
226     if (m_ownsSrc)
227       m_ownsSrc = false;  // Transfer m_src and ownership if it
228     else                  // was surrendered
229     {
230       assert(m_src->getRefCount() >
231              1);  // We'll be linking params to the cloned
232                   // fx - so it MUST NOT be destroyed on release
233       TFx *src = m_src.getPointer();
234       m_src    = m_src->clone(false);  // Duplicate and link parameters all
235       m_src->linkParams(src);          // the following times
236     }
237 
238     return m_src.getPointer();
239   }
240 };
241 
242 }  // namespace
243 
244 //**********************************************************************
245 //    FxCommandUndo  definition
246 //**********************************************************************
247 
248 class FxCommandUndo : public TUndo {
249 public:
~FxCommandUndo()250   virtual ~FxCommandUndo() {}
251 
252   virtual bool isConsistent() const = 0;
253 
254 public:
255   template <typename Pred>
256   static TFx *leftmostConnectedFx(TFx *fx, Pred pred);
257   template <typename Pred>
258   static TFx *rightmostConnectedFx(TFx *fx, Pred pred);
259 
260   static TFx *leftmostConnectedFx(TFx *fx);
261   static TFx *rightmostConnectedFx(TFx *fx);
262 
263   static std::vector<TFxCommand::Link> inputLinks(TXsheet *xsh, TFx *fx);
264   static std::vector<TFxCommand::Link> outputLinks(TXsheet *xsh, TFx *fx);
265 
getHistoryType()266   int getHistoryType() override { return HistoryType::Schematic; }
267 
268 protected:
269   static TXshZeraryFxColumn *createZeraryFxColumn(TXsheet *xsh, TFx *zfx,
270                                                   int row = 0);
271   static void cloneGroupStack(const QStack<int> &groupIds,
272                               const QStack<std::wstring> &groupNames,
273                               TFx *toFx);
274   static void cloneGroupStack(TFx *fromFx, TFx *toFx);
275   static void copyGroupEditLevel(int editGroupId, TFx *toFx);
276   static void copyGroupEditLevel(TFx *fromFx, TFx *toFx);
277   static void copyDagPosition(TFx *fromFx, TFx *toFx);
278   static void attach(TXsheet *xsh, TFx *inputFx, TFx *outputFx, int port,
279                      bool copyGroupData);
280   static void attach(TXsheet *xsh, const TFxCommand::Link &link,
281                      bool copyGroupData);
282   static void attachOutputs(TXsheet *xsh, TFx *insertedFx, TFx *inputFx);
283   static void detachFxs(TXsheet *xsh, TFx *fxLeft, TFx *fxRight,
284                         bool detachLeft = true);
285   static void insertFxs(TXsheet *xsh, const TFxCommand::Link &link, TFx *fxLeft,
286                         TFx *fxRight);
287   static void insertColumn(TXsheet *xsh, TXshColumn *column, int colIdx,
288                            bool removeHole = false, bool autoTerminal = false);
289   static void removeFxOrColumn(TXsheet *xsh, TFx *fx, int colIdx,
290                                bool insertHole   = false,
291                                bool unlinkParams = true);
292   static void linkParams(TFx *fx, TFx *linkedFx);
293   static void unlinkParams(TFx *fx);
294   static void makeNotCurrent(TFxHandle *fxHandle, TFx *fx);
295 
296 private:
297   static void removeColumn(TXsheet *xsh, int colIdx, bool insertHole);
298   static void removeNormalFx(TXsheet *xsh, TFx *fx);
299   static void removeOutputFx(TXsheet *xsh, TOutputFx *outputFx);
300 };
301 
302 //------------------------------------------------------
303 
createZeraryFxColumn(TXsheet * xsh,TFx * zfx,int row)304 TXshZeraryFxColumn *FxCommandUndo::createZeraryFxColumn(TXsheet *xsh, TFx *zfx,
305                                                         int row) {
306   int frameCount = xsh->getScene()->getFrameCount() - row;
307 
308   TXshZeraryFxColumn *column =
309       new TXshZeraryFxColumn(frameCount > 0 ? frameCount : 100);
310   column->getZeraryColumnFx()->setZeraryFx(zfx);
311   column->insertEmptyCells(0, row);
312 
313   return column;
314 }
315 
316 //------------------------------------------------------
317 
cloneGroupStack(const QStack<int> & groupIds,const QStack<std::wstring> & groupNames,TFx * toFx)318 void FxCommandUndo::cloneGroupStack(const QStack<int> &groupIds,
319                                     const QStack<std::wstring> &groupNames,
320                                     TFx *toFx) {
321   toFx->getAttributes()->removeFromAllGroup();
322 
323   for (int i = 0; i < groupIds.size(); ++i) {
324     toFx->getAttributes()->setGroupId(groupIds[i]);
325     toFx->getAttributes()->setGroupName(groupNames[i]);
326   }
327 }
328 
329 //------------------------------------------------------
330 
cloneGroupStack(TFx * fromFx,TFx * toFx)331 void FxCommandUndo::cloneGroupStack(TFx *fromFx, TFx *toFx) {
332   if (fromFx->getAttributes()->isGrouped()) {
333     cloneGroupStack(fromFx->getAttributes()->getGroupIdStack(),
334                     fromFx->getAttributes()->getGroupNameStack(), toFx);
335   }
336 }
337 
338 //------------------------------------------------------
339 
copyGroupEditLevel(int editGroupId,TFx * toFx)340 void FxCommandUndo::copyGroupEditLevel(int editGroupId, TFx *toFx) {
341   toFx->getAttributes()->closeAllGroups();
342   while (editGroupId != toFx->getAttributes()->getEditingGroupId() &&
343          toFx->getAttributes()->editGroup())
344     ;
345 
346   assert(editGroupId == toFx->getAttributes()->getEditingGroupId());
347 }
348 
349 //------------------------------------------------------
350 
copyGroupEditLevel(TFx * fromFx,TFx * toFx)351 void FxCommandUndo::copyGroupEditLevel(TFx *fromFx, TFx *toFx) {
352   assert(toFx);
353   if (fromFx && fromFx->getAttributes()->isGrouped())
354     copyGroupEditLevel(fromFx->getAttributes()->getEditingGroupId(), toFx);
355 }
356 
357 //------------------------------------------------------
358 
copyDagPosition(TFx * fromFx,TFx * toFx)359 void FxCommandUndo::copyDagPosition(TFx *fromFx, TFx *toFx) {
360   assert(toFx);
361   if (fromFx)
362     toFx->getAttributes()->setDagNodePos(
363         fromFx->getAttributes()->getDagNodePos());
364 }
365 
366 //------------------------------------------------------
367 
attach(TXsheet * xsh,TFx * inputFx,TFx * outputFx,int link,bool copyGroupData)368 void FxCommandUndo::attach(TXsheet *xsh, TFx *inputFx, TFx *outputFx, int link,
369                            bool copyGroupData) {
370   if (outputFx) {
371     FxDag *fxDag = xsh->getFxDag();
372 
373     inputFx  = ::getActualOut(inputFx);
374     outputFx = ::getActualIn(outputFx);
375 
376     if (inputFx && link < 0) {
377       assert(dynamic_cast<TXsheetFx *>(outputFx));
378       fxDag->addToXsheet(inputFx);
379     } else {
380       int ipCount = outputFx->getInputPortCount();
381       if (ipCount > 0 && link < ipCount)
382         outputFx->getInputPort(link)->setFx(inputFx);
383 
384       if (copyGroupData) copyGroupEditLevel(inputFx, outputFx);
385     }
386   }
387 }
388 
389 //------------------------------------------------------
390 
attach(TXsheet * xsh,const TFxCommand::Link & link,bool copyGroupData)391 void FxCommandUndo::attach(TXsheet *xsh, const TFxCommand::Link &link,
392                            bool copyGroupData) {
393   attach(xsh, link.m_inputFx.getPointer(), link.m_outputFx.getPointer(),
394          link.m_index, copyGroupData);
395 }
396 
397 //------------------------------------------------------
398 
attachOutputs(TXsheet * xsh,TFx * insertedFx,TFx * inputFx)399 void FxCommandUndo::attachOutputs(TXsheet *xsh, TFx *insertedFx, TFx *inputFx) {
400   TCG_ASSERT(inputFx, return );
401 
402   FxDag *fxDag = xsh->getFxDag();
403 
404   insertedFx = ::getActualOut(insertedFx);
405   inputFx    = ::getActualOut(inputFx);
406 
407   int p, pCount = inputFx->getOutputConnectionCount();
408   for (p = pCount - 1; p >= 0;
409        --p)  // Backward iteration on output connections -
410   {          // it's necessary since TFxPort::setFx() REMOVES
411     TFxPort *port = inputFx->getOutputConnection(
412         p);  // the corresponding port int the output connections
413     port->setFx(
414         insertedFx);  // container - thus, it's better to start from the end
415   }
416 
417   if (fxDag->getTerminalFxs()->containsFx(inputFx)) {
418     fxDag->removeFromXsheet(inputFx);
419     fxDag->addToXsheet(insertedFx);
420   }
421 }
422 
423 //------------------------------------------------------
424 
detachFxs(TXsheet * xsh,TFx * fxLeft,TFx * fxRight,bool detachLeft)425 void FxCommandUndo::detachFxs(TXsheet *xsh, TFx *fxLeft, TFx *fxRight,
426                               bool detachLeft) {
427   assert(fxLeft && fxRight);
428 
429   fxLeft  = ::getActualIn(fxLeft);
430   fxRight = ::getActualOut(fxRight);
431 
432   int ipCount = fxLeft->getInputPortCount();
433 
434   // Redirect input/output ports
435   TFx *inputFx0 = (ipCount > 0) ? fxLeft->getInputPort(0)->getFx() : 0;
436 
437   int p, opCount = fxRight->getOutputConnectionCount();
438   for (p = opCount - 1; p >= 0;
439        --p)  // Backward iteration due to TFxPort::setFx()
440   {
441     TFxPort *outPort = fxRight->getOutputConnection(p);
442     assert(outPort && outPort->getFx() == fxRight);
443 
444     outPort->setFx(inputFx0);
445   }
446 
447   // Xsheet links redirection
448   FxDag *fxDag = xsh->getFxDag();
449   if (fxDag->getTerminalFxs()->containsFx(fxRight)) {
450     fxDag->removeFromXsheet(fxRight);
451 
452     for (int p = 0; p != ipCount; ++p)
453       if (TFx *inputFx = fxLeft->getInputPort(p)->getFx())
454         fxDag->addToXsheet(inputFx);
455   }
456 
457   if (detachLeft) fxLeft->disconnectAll();
458 }
459 
460 //------------------------------------------------------
461 
insertFxs(TXsheet * xsh,const TFxCommand::Link & link,TFx * fxLeft,TFx * fxRight)462 void FxCommandUndo::insertFxs(TXsheet *xsh, const TFxCommand::Link &link,
463                               TFx *fxLeft, TFx *fxRight) {
464   assert(fxLeft && fxRight);
465 
466   if (link.m_inputFx && link.m_outputFx) {
467     FxCommandUndo::attach(xsh, link.m_inputFx.getPointer(), fxLeft, 0, false);
468     FxCommandUndo::attach(xsh, fxRight, link.m_outputFx.getPointer(),
469                           link.m_index, false);
470 
471     if (link.m_index < 0)
472       xsh->getFxDag()->removeFromXsheet(
473           ::getActualOut(link.m_inputFx.getPointer()));
474   }
475 }
476 
477 //------------------------------------------------------
478 
insertColumn(TXsheet * xsh,TXshColumn * column,int col,bool removeHole,bool autoTerminal)479 void FxCommandUndo::insertColumn(TXsheet *xsh, TXshColumn *column, int col,
480                                  bool removeHole, bool autoTerminal) {
481   FxDag *fxDag  = xsh->getFxDag();
482   TFx *fx       = column->getFx();
483   bool terminal = false;
484 
485   if (fx) {
486     ::showFx(xsh, fx);
487     terminal = fxDag->getTerminalFxs()->containsFx(fx);
488   }
489 
490   if (removeHole) xsh->removeColumn(col);
491 
492   xsh->insertColumn(col, column);  // Attaches the fx to the xsheet, too -
493                                    // but not if the column is a palette one.
494   if (!autoTerminal) {
495     // Preserve the initial terminal state.
496     // This lets fxs to be linked to the xsheet while still hidden.
497 
498     fxDag->removeFromXsheet(fx);
499     if (terminal) fxDag->addToXsheet(fx);
500   }
501 
502   xsh->updateFrameCount();
503 }
504 
505 //------------------------------------------------------
506 
removeColumn(TXsheet * xsh,int col,bool insertHole)507 void FxCommandUndo::removeColumn(TXsheet *xsh, int col, bool insertHole) {
508   if (TFx *colFx = xsh->getColumn(col)->getFx()) {
509     detachFxs(xsh, colFx, colFx);
510     ::hideFx(xsh, colFx);
511   }
512 
513   xsh->removeColumn(col);    // Already detaches any fx in output,
514   if (insertHole)            // including the terminal case
515     xsh->insertColumn(col);  // Note that fxs in output are not
516                              // removed - just detached.
517   xsh->updateFrameCount();
518 }
519 
520 //------------------------------------------------------
521 
removeNormalFx(TXsheet * xsh,TFx * fx)522 void FxCommandUndo::removeNormalFx(TXsheet *xsh, TFx *fx) {
523   detachFxs(xsh, fx, fx);
524   ::removeFxFromCurrentScene(fx, xsh);  // Already hideFx()s
525 }
526 
527 //------------------------------------------------------
528 
removeOutputFx(TXsheet * xsh,TOutputFx * outputFx)529 void FxCommandUndo::removeOutputFx(TXsheet *xsh, TOutputFx *outputFx) {
530   detachFxs(xsh, outputFx, outputFx);
531   xsh->getFxDag()->removeOutputFx(outputFx);
532 }
533 
534 //------------------------------------------------------
535 
linkParams(TFx * fx,TFx * linkedFx)536 void FxCommandUndo::linkParams(TFx *fx, TFx *linkedFx) {
537   if (linkedFx) ::getActualIn(fx)->linkParams(::getActualIn(linkedFx));
538 }
539 
540 //------------------------------------------------------
541 
unlinkParams(TFx * fx)542 void FxCommandUndo::unlinkParams(TFx *fx) {
543   if (fx = ::getActualIn(fx), fx->getLinkedFx()) fx->unlinkParams();
544 }
545 
546 //------------------------------------------------------
547 
makeNotCurrent(TFxHandle * fxHandle,TFx * fx)548 void FxCommandUndo::makeNotCurrent(TFxHandle *fxHandle, TFx *fx) {
549   if (fx = ::getActualOut(fx), fx == fxHandle->getFx()) fxHandle->setFx(0);
550 }
551 
552 //------------------------------------------------------
553 
removeFxOrColumn(TXsheet * xsh,TFx * fx,int colIdx,bool insertHole,bool unlinkParams)554 void FxCommandUndo::removeFxOrColumn(TXsheet *xsh, TFx *fx, int colIdx,
555                                      bool insertHole, bool unlinkParams) {
556   assert(fx || colIdx >= 0);
557 
558   if (!fx)
559     fx = xsh->getColumn(colIdx)->getFx();
560   else if (TColumnFx *colFx = dynamic_cast<TColumnFx *>(fx))
561     colIdx = colFx->getColumnIndex();
562   else if (TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx)) {
563     if (zfx->getColumnFx())
564       fx     = zfx->getColumnFx(),
565       colIdx = static_cast<TColumnFx *>(fx)->getColumnIndex();
566   }
567 
568   if (fx) {
569     // Discriminate special fx types
570     if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx)) {
571       // Removed as a column
572       fx = zcfx->getZeraryFx();
573     } else if (TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx)) {
574       assert(xsh->getFxDag()->getOutputFxCount() > 1);
575       FxCommandUndo::removeOutputFx(xsh, outputFx);
576     } else if (colIdx < 0)
577       FxCommandUndo::removeNormalFx(xsh, fx);
578 
579     if (unlinkParams) FxCommandUndo::unlinkParams(fx);
580   }
581 
582   if (colIdx >= 0) FxCommandUndo::removeColumn(xsh, colIdx, insertHole);
583 }
584 
585 //------------------------------------------------------
586 
587 template <typename Pred>
leftmostConnectedFx(TFx * fx,Pred pred)588 TFx *FxCommandUndo::leftmostConnectedFx(TFx *fx, Pred pred) {
589   assert(fx);
590 
591   fx = rightmostConnectedFx(fx, pred);  // The rightmost fx should be discovered
592                                         // first, then, we'll descend from that
593   do {
594     fx = ::getActualIn(fx);
595 
596     if (!((fx->getInputPortCount() > 0) && fx->getInputPort(0)->isConnected() &&
597           pred(fx->getInputPort(0)->getFx())))
598       break;
599 
600     fx = fx->getInputPort(0)->getFx();
601   } while (true);
602 
603   return fx;
604 }
605 
606 //------------------------------------------------------
607 
608 template <typename Pred>
rightmostConnectedFx(TFx * fx,Pred pred)609 TFx *FxCommandUndo::rightmostConnectedFx(TFx *fx, Pred pred) {
610   assert(fx);
611 
612   do {
613     fx = ::getActualOut(fx);
614 
615     if (!(fx->getOutputConnectionCount() > 0 &&
616           pred(fx->getOutputConnection(0)->getOwnerFx())))
617       break;
618 
619     fx = fx->getOutputConnection(0)->getOwnerFx();
620   } while (true);
621 
622   return fx;
623 }
624 
625 //------------------------------------------------------
626 
627 namespace {
628 struct True_pred {
operator ()__anon13f77d830411::True_pred629   bool operator()(TFx *fx) { return true; }
630 };
631 }  // namespace
632 
leftmostConnectedFx(TFx * fx)633 TFx *FxCommandUndo::leftmostConnectedFx(TFx *fx) {
634   return leftmostConnectedFx(fx, ::True_pred());
635 }
636 
637 //------------------------------------------------------
638 
rightmostConnectedFx(TFx * fx)639 TFx *FxCommandUndo::rightmostConnectedFx(TFx *fx) {
640   return rightmostConnectedFx(fx, ::True_pred());
641 }
642 
643 //------------------------------------------------------
644 
inputLinks(TXsheet * xsh,TFx * fx)645 std::vector<TFxCommand::Link> FxCommandUndo::inputLinks(TXsheet *xsh, TFx *fx) {
646   std::vector<TFxCommand::Link> result;
647 
648   fx = ::getActualIn(fx);
649 
650   int il, ilCount = fx->getInputPortCount();
651   for (il = 0; il != ilCount; ++il) {
652     TFxPort *port = fx->getInputPort(il);
653 
654     assert(port);
655     if (port->isConnected())
656       result.push_back(TFxCommand::Link(port->getFx(), fx, il));
657   }
658 
659   return result;
660 }
661 
662 //------------------------------------------------------
663 
outputLinks(TXsheet * xsh,TFx * fx)664 std::vector<TFxCommand::Link> FxCommandUndo::outputLinks(TXsheet *xsh,
665                                                          TFx *fx) {
666   std::vector<TFxCommand::Link> result;
667 
668   fx = ::getActualOut(fx);
669 
670   int ol, olCount = fx->getOutputConnectionCount();
671   for (ol = 0; ol != olCount; ++ol) {
672     TFxPort *port = fx->getOutputConnection(ol);
673     TFx *ownerFx  = port->getOwnerFx();
674     int portIndex = ::inputPortIndex(ownerFx, port);
675 
676     result.push_back(TFxCommand::Link(fx, ownerFx, portIndex));
677   }
678 
679   FxDag *fxDag = xsh->getFxDag();
680   if (fxDag->getTerminalFxs()->containsFx(fx))
681     result.push_back(TFxCommand::Link(fx, fxDag->getXsheetFx(), -1));
682 
683   return result;
684 }
685 
686 //**********************************************************************
687 //    Insert Fx  command
688 //**********************************************************************
689 
690 class InsertFxUndo final : public FxCommandUndo {
691   QList<TFxP> m_selectedFxs;
692   QList<TFxCommand::Link> m_selectedLinks;
693 
694   TApplication *m_app;
695 
696   QList<TFxP> m_insertedFxs;
697   TXshZeraryFxColumnP m_insertedColumn;
698   int m_colIdx;
699   bool m_columnReplacesHole;
700   bool m_attachOutputs;
701 
702 public:
InsertFxUndo(const TFxP & fx,int row,int col,const QList<TFxP> & selectedFxs,QList<TFxCommand::Link> selectedLinks,TApplication * app,bool attachOutputs=true)703   InsertFxUndo(const TFxP &fx, int row, int col, const QList<TFxP> &selectedFxs,
704                QList<TFxCommand::Link> selectedLinks, TApplication *app,
705                bool attachOutputs = true)
706       : m_selectedFxs(selectedFxs)
707       , m_selectedLinks(selectedLinks)
708       , m_insertedColumn(0)
709       , m_app(app)
710       , m_colIdx(col)
711       , m_columnReplacesHole(false)
712       , m_attachOutputs(attachOutputs) {
713     initialize(fx, row, col);
714   }
715 
isConsistent() const716   bool isConsistent() const override { return !m_insertedFxs.isEmpty(); }
717 
718   void redo() const override;
719   void undo() const override;
720 
getSize() const721   int getSize() const override { return sizeof(*this); }
722 
723   QString getHistoryString() override;
724 
725 private:
726   void initialize(const TFxP &newFx, int row, int col);
727 };
728 
729 //------------------------------------------------------
730 
has_fx_column(TFx * fx)731 inline bool has_fx_column(TFx *fx) {
732   if (TPluginInterface *plgif = dynamic_cast<TPluginInterface *>(fx))
733     return plgif->isPluginZerary();
734   else if (TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx))
735     return zfx->isZerary();
736   return false;
737 }
738 
739 namespace {
740 
containsInputFx(const QList<TFxP> & fxs,const TFxCommand::Link & link)741 bool containsInputFx(const QList<TFxP> &fxs, const TFxCommand::Link &link) {
742   return fxs.contains(link.m_inputFx);
743 }
744 
745 }  // namespace
746 
initialize(const TFxP & newFx,int row,int col)747 void InsertFxUndo::initialize(const TFxP &newFx, int row, int col) {
748   struct Locals {
749     InsertFxUndo *m_this;
750     inline void storeFx(TXsheet *xsh, TFx *fx) {
751       ::initializeFx(xsh, fx);
752       m_this->m_insertedFxs.push_back(fx);
753     }
754 
755   } locals = {this};
756 
757   TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
758   TFx *fx      = newFx.getPointer();
759 
760   assert(!dynamic_cast<TZeraryColumnFx *>(fx));
761 
762   TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx);
763   if (has_fx_column(fx)) {
764     m_insertedColumn = InsertFxUndo::createZeraryFxColumn(xsh, fx, row);
765 
766     locals.storeFx(xsh, fx);
767 
768     if (xsh->getColumn(col) && xsh->getColumn(col)->isEmpty())
769       m_columnReplacesHole = true;
770   } else {
771     if (m_selectedFxs.isEmpty() && m_selectedLinks.isEmpty()) {
772       // Attempt retrieval of current Fx from the fxHandle
773       if (TFx *currentFx = m_app->getCurrentFx()->getFx())
774         m_selectedFxs.push_back(currentFx);
775       else {
776         // Isolated case
777         locals.storeFx(xsh, fx);
778         return;
779       }
780     }
781 
782     // Remove all unacceptable input fxs
783     ::FilterInsideAMacro filterInMacroFxs = {xsh};
784     m_selectedFxs.erase(std::remove_if(m_selectedFxs.begin(),
785                                        m_selectedFxs.end(), filterInMacroFxs),
786                         m_selectedFxs.end());
787 
788     // Remove all unacceptable links or links whose input fx was already
789     // selected
790     m_selectedLinks.erase(
791         std::remove_if(m_selectedLinks.begin(), m_selectedLinks.end(),
792                        filterInMacroFxs),
793         m_selectedLinks.end());
794     m_selectedLinks.erase(
795         std::remove_if(m_selectedLinks.begin(), m_selectedLinks.end(),
796                        [this](const TFxCommand::Link &link) {
797                          return containsInputFx(m_selectedFxs, link);
798                        }),
799         m_selectedLinks.end());
800 
801     // Build an fx for each of the specified inputs
802     ::CloneFxFunctor cloneFx = {fx, true};
803 
804     int f, fCount = m_selectedFxs.size();
805     for (f = 0; f != fCount; ++f) {
806       TFx *fx = cloneFx();
807       FxCommandUndo::cloneGroupStack(m_selectedFxs[f].getPointer(), fx);
808       locals.storeFx(xsh, fx);
809     }
810 
811     fCount = m_selectedLinks.size();
812     for (f = 0; f != fCount; ++f) {
813       TFx *fx = cloneFx();
814       FxCommandUndo::cloneGroupStack(m_selectedLinks[f].m_inputFx.getPointer(),
815                                      fx);
816       locals.storeFx(xsh, fx);
817     }
818   }
819 }
820 
821 //------------------------------------------------------
822 
redo() const823 void InsertFxUndo::redo() const {
824   struct OnExit {
825     const InsertFxUndo *m_this;
826     ~OnExit() {
827       m_this->m_app->getCurrentFx()->setFx(
828           m_this->m_insertedFxs.back().getPointer());
829       m_this->m_app->getCurrentXsheet()->notifyXsheetChanged();
830       m_this->m_app->getCurrentScene()->setDirtyFlag(true);
831     }
832   } onExit = {this};
833 
834   TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
835 
836   // Zerary case
837   if (m_insertedColumn) {
838     FxCommandUndo::insertColumn(xsh, m_insertedColumn.getPointer(), m_colIdx,
839                                 m_columnReplacesHole, true);
840     return;
841   }
842 
843   // Isolated Fx case
844   if (m_selectedLinks.isEmpty() && m_selectedFxs.isEmpty()) {
845     assert(m_insertedFxs.size() == 1);
846     ::addFxToCurrentScene(m_insertedFxs.back().getPointer(), xsh,
847                           false);  // Already showFx()s
848   } else {
849     // Selected links
850     int i;
851     for (i = 0; i < m_selectedLinks.size(); ++i) {
852       const TFxCommand::Link &link = m_selectedLinks[i];
853       TFx *insertedFx              = m_insertedFxs[i].getPointer();
854 
855       ::addFxToCurrentScene(insertedFx, xsh, false);
856       FxCommandUndo::insertFxs(xsh, link, insertedFx, insertedFx);
857       FxCommandUndo::copyGroupEditLevel(link.m_inputFx.getPointer(),
858                                         insertedFx);
859     }
860 
861     // Selected fxs
862     int j, t;
863     for (j = 0, t = 0; j < m_selectedFxs.size(); j++) {
864       TFx *fx = m_selectedFxs[j].getPointer();
865       assert(fx);
866 
867       TFx *insertedFx = m_insertedFxs[i + t].getPointer();
868       t++;
869 
870       assert(insertedFx);
871       ::addFxToCurrentScene(insertedFx, xsh, false);
872 
873       if (m_attachOutputs) FxCommandUndo::attachOutputs(xsh, insertedFx, fx);
874 
875       FxCommandUndo::attach(xsh, fx, insertedFx, 0, true);
876     }
877   }
878 }
879 
880 //------------------------------------------------------
881 
undo() const882 void InsertFxUndo::undo() const {
883   TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
884 
885   int i, iCount = m_insertedFxs.size();
886   for (i = 0; i != iCount; ++i) {
887     TFx *insertedFx = m_insertedFxs[i].getPointer();
888 
889     FxCommandUndo::removeFxOrColumn(xsh, insertedFx, -1, m_columnReplacesHole,
890                                     false);  // Skip parameter links removal
891     FxCommandUndo::makeNotCurrent(m_app->getCurrentFx(), insertedFx);
892   }
893 
894   m_app->getCurrentFx()->setFx(0);
895   m_app->getCurrentXsheet()->notifyXsheetChanged();
896   m_app->getCurrentScene()->setDirtyFlag(true);
897 }
898 
899 //------------------------------------------------------
900 
getHistoryString()901 QString InsertFxUndo::getHistoryString() {
902   QString str = (m_selectedLinks.isEmpty()) ? QObject::tr("Add Fx  : ")
903                                             : QObject::tr("Insert Fx  : ");
904   QList<TFxP>::iterator it;
905   for (it = m_insertedFxs.begin(); it != m_insertedFxs.end(); it++) {
906     if (it != m_insertedFxs.begin()) str += QString(", ");
907 
908     str += QString::fromStdWString((*it)->getFxId());
909   }
910   return str;
911 }
912 
913 //=============================================================
914 
insertFx(TFx * newFx,const QList<TFxP> & fxs,const QList<Link> & links,TApplication * app,int col,int row)915 void TFxCommand::insertFx(TFx *newFx, const QList<TFxP> &fxs,
916                           const QList<Link> &links, TApplication *app, int col,
917                           int row) {
918   if (!newFx) return;
919 
920   if (col < 0)
921     col = 0;  // Normally insert before. In case of camera, insert after
922 
923   std::unique_ptr<FxCommandUndo> undo(
924       new InsertFxUndo(newFx, row, col, fxs, links, app));
925   if (undo->isConsistent()) {
926     undo->redo();
927     TUndoManager::manager()->add(undo.release());
928   }
929 }
930 
931 //**********************************************************************
932 //    Add Fx  command
933 //**********************************************************************
934 
addFx(TFx * newFx,const QList<TFxP> & fxs,TApplication * app,int col,int row)935 void TFxCommand::addFx(TFx *newFx, const QList<TFxP> &fxs, TApplication *app,
936                        int col, int row) {
937   if (!newFx) return;
938 
939   std::unique_ptr<FxCommandUndo> undo(
940       new InsertFxUndo(newFx, row, col, fxs, QList<Link>(), app, false));
941   if (!undo->isConsistent()) return;
942 
943   undo->redo();
944   TUndoManager::manager()->add(undo.release());
945 }
946 
947 //**********************************************************************
948 //    Duplicate Fx  command
949 //**********************************************************************
950 
951 class DuplicateFxUndo final : public FxCommandUndo {
952   TFxP m_fx, m_dupFx;
953   TXshColumnP m_column;
954   int m_colIdx;
955 
956   TXsheetHandle *m_xshHandle;
957   TFxHandle *m_fxHandle;
958 
959 public:
DuplicateFxUndo(const TFxP & originalFx,TXsheetHandle * xshHandle,TFxHandle * fxHandle)960   DuplicateFxUndo(const TFxP &originalFx, TXsheetHandle *xshHandle,
961                   TFxHandle *fxHandle)
962       : m_fx(originalFx)
963       , m_colIdx(-1)
964       , m_xshHandle(xshHandle)
965       , m_fxHandle(fxHandle) {
966     initialize();
967   }
968 
isConsistent() const969   bool isConsistent() const override { return bool(m_dupFx); }
970 
971   void redo() const override;
972   void undo() const override;
973 
getSize() const974   int getSize() const override { return sizeof(*this); }
975 
976   QString getHistoryString() override;
977 
978 private:
979   void initialize();
980 };
981 
982 //-------------------------------------------------------------
983 
initialize()984 void DuplicateFxUndo::initialize() {
985   TXsheet *xsh = m_xshHandle->getXsheet();
986   TFx *fx      = m_fx.getPointer();
987 
988   fx = ::getActualOut(fx);
989 
990   if (isInsideAMacroFx(fx, xsh) || dynamic_cast<TXsheetFx *>(fx) ||
991       dynamic_cast<TOutputFx *>(fx) ||
992       (dynamic_cast<TColumnFx *>(fx) && !dynamic_cast<TZeraryColumnFx *>(fx)))
993     return;
994 
995   if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx)) {
996     m_column = new TXshZeraryFxColumn(*zcfx->getColumn());
997     m_colIdx = xsh->getFirstFreeColumnIndex();
998 
999     TZeraryColumnFx *dupZcfx =
1000         static_cast<TZeraryColumnFx *>(m_column->getFx());
1001     ::initializeFx(xsh, dupZcfx->getZeraryFx());
1002 
1003     FxCommandUndo::cloneGroupStack(zcfx, dupZcfx);
1004 
1005     m_dupFx = dupZcfx;
1006   } else {
1007     fx = fx->clone(false);
1008     ::initializeFx(xsh, fx);
1009 
1010     FxCommandUndo::cloneGroupStack(m_fx.getPointer(), fx);
1011 
1012     m_dupFx = fx;
1013   }
1014 }
1015 
1016 //-------------------------------------------------------------
1017 
redo() const1018 void DuplicateFxUndo::redo() const {
1019   TXsheet *xsh = m_xshHandle->getXsheet();
1020 
1021   if (m_column) {
1022     // Zerary Fx case
1023     TZeraryColumnFx *zcfx = static_cast<TZeraryColumnFx *>(m_fx.getPointer());
1024     TZeraryColumnFx *dupZcfx =
1025         static_cast<TZeraryColumnFx *>(m_dupFx.getPointer());
1026 
1027     FxCommandUndo::insertColumn(xsh, m_column.getPointer(), m_colIdx, true,
1028                                 true);
1029     FxCommandUndo::copyGroupEditLevel(zcfx, dupZcfx);
1030 
1031     dupZcfx->getZeraryFx()->linkParams(zcfx->getZeraryFx());
1032   } else {
1033     // Normal Fx case
1034     addFxToCurrentScene(m_dupFx.getPointer(), m_xshHandle->getXsheet(), false);
1035     FxCommandUndo::copyGroupEditLevel(m_fx.getPointer(), m_dupFx.getPointer());
1036 
1037     m_dupFx->linkParams(m_fx.getPointer());
1038   }
1039 
1040   m_fxHandle->setFx(m_dupFx.getPointer());
1041   m_xshHandle->notifyXsheetChanged();
1042 }
1043 
1044 //-------------------------------------------------------------
1045 
undo() const1046 void DuplicateFxUndo::undo() const {
1047   FxCommandUndo::removeFxOrColumn(m_xshHandle->getXsheet(),
1048                                   m_dupFx.getPointer(), -1, true, true);
1049 
1050   m_fxHandle->setFx(0);
1051   m_xshHandle->notifyXsheetChanged();
1052 }
1053 
1054 //-------------------------------------------------------------
1055 
getHistoryString()1056 QString DuplicateFxUndo::getHistoryString() {
1057   if (TZeraryColumnFx *zDup =
1058           dynamic_cast<TZeraryColumnFx *>(m_dupFx.getPointer()))
1059     return QObject::tr("Create Linked Fx  : %1")
1060         .arg(QString::fromStdWString(zDup->getZeraryFx()->getFxId()));
1061 
1062   return QObject::tr("Create Linked Fx  : %1")
1063       .arg(QString::fromStdWString(m_dupFx->getFxId()));
1064 }
1065 
1066 //=============================================================
1067 
duplicateFx(TFx * src,TXsheetHandle * xshHandle,TFxHandle * fxHandle)1068 void TFxCommand::duplicateFx(TFx *src, TXsheetHandle *xshHandle,
1069                              TFxHandle *fxHandle) {
1070   std::unique_ptr<FxCommandUndo> undo(
1071       new DuplicateFxUndo(src, xshHandle, fxHandle));
1072   if (undo->isConsistent()) {
1073     undo->redo();
1074     TUndoManager::manager()->add(undo.release());
1075   }
1076 }
1077 
1078 //**********************************************************************
1079 //    Replace Fx  command
1080 //**********************************************************************
1081 
1082 class ReplaceFxUndo final : public FxCommandUndo {
1083   TFxP m_fx, m_repFx, m_linkedFx;
1084   TXshColumnP m_column, m_repColumn;
1085   int m_colIdx, m_repColIdx;
1086 
1087   std::vector<std::pair<int, TFx *>> m_inputLinks;
1088 
1089   TXsheetHandle *m_xshHandle;
1090   TFxHandle *m_fxHandle;
1091 
1092 public:
ReplaceFxUndo(const TFxP & replacementFx,const TFxP & replacedFx,TXsheetHandle * xshHandle,TFxHandle * fxHandle)1093   ReplaceFxUndo(const TFxP &replacementFx, const TFxP &replacedFx,
1094                 TXsheetHandle *xshHandle, TFxHandle *fxHandle)
1095       : m_fx(replacedFx)
1096       , m_repFx(replacementFx)
1097       , m_xshHandle(xshHandle)
1098       , m_fxHandle(fxHandle)
1099       , m_colIdx(-1)
1100       , m_repColIdx(-1) {
1101     initialize();
1102   }
1103 
isConsistent() const1104   bool isConsistent() const override { return bool(m_repFx); }
1105 
1106   void redo() const override;
1107   void undo() const override;
1108 
getSize() const1109   int getSize() const override { return sizeof(*this); }
1110 
1111   QString getHistoryString() override;
1112 
1113 private:
1114   void initialize();
1115   static void replace(TXsheet *xsh, TFx *fx, TFx *repFx, TXshColumn *column,
1116                       TXshColumn *repColumn, int colIdx, int repColIdx);
1117 };
1118 
1119 //-------------------------------------------------------------
1120 
initialize()1121 void ReplaceFxUndo::initialize() {
1122   TXsheet *xsh = m_xshHandle->getXsheet();
1123 
1124   TFx *fx    = m_fx.getPointer();
1125   TFx *repFx = m_repFx.getPointer();
1126 
1127   fx = ::getActualOut(fx);
1128 
1129   if (isInsideAMacroFx(fx, xsh) || dynamic_cast<TXsheetFx *>(fx) ||
1130       dynamic_cast<TOutputFx *>(fx) ||
1131       (dynamic_cast<TColumnFx *>(fx) && !dynamic_cast<TZeraryColumnFx *>(fx))) {
1132     m_repFx = TFxP();
1133     return;
1134   }
1135 
1136   if (dynamic_cast<TXsheetFx *>(repFx) || dynamic_cast<TOutputFx *>(repFx) ||
1137       dynamic_cast<TColumnFx *>(repFx)) {
1138     m_repFx = TFxP();
1139     return;
1140   }
1141 
1142   ::initializeFx(xsh, repFx);
1143 
1144   TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx);
1145   if (zcfx) {
1146     TXshZeraryFxColumn *zfColumn = zcfx->getColumn();
1147 
1148     m_column = zfColumn;
1149     m_colIdx = zfColumn->getIndex();
1150 
1151     fx = zcfx->getZeraryFx();
1152   }
1153 
1154   TZeraryColumnFx *zcrepfx = dynamic_cast<TZeraryColumnFx *>(repFx);
1155   if (zcrepfx) repFx = zcrepfx->getZeraryFx();
1156 
1157   bool fxHasCol    = has_fx_column(fx);
1158   bool repfxHasCol = has_fx_column(repFx);
1159 
1160   if (fxHasCol && repfxHasCol) {
1161     if (zcfx) {
1162       // Build a column with the same source cells pattern
1163       m_repColumn = new TXshZeraryFxColumn(*zcfx->getColumn());
1164       m_repColIdx = m_colIdx;
1165 
1166       // Substitute the column's zerary fx with the substitute one
1167       TZeraryColumnFx *repZcfx =
1168           static_cast<TZeraryColumnFx *>(m_repColumn->getFx());
1169       repZcfx->setZeraryFx(repFx);
1170 
1171       FxCommandUndo::cloneGroupStack(zcfx, repZcfx);
1172       m_repFx = repZcfx;
1173     } else {
1174       m_repColumn = FxCommandUndo::createZeraryFxColumn(xsh, repFx);
1175       m_repColIdx = xsh->getFirstFreeColumnIndex();
1176       m_repFx     = static_cast<TZeraryColumnFx *>(m_repColumn->getFx());
1177     }
1178   } else if (!fxHasCol && repfxHasCol) {
1179     m_repColumn = FxCommandUndo::createZeraryFxColumn(xsh, repFx);
1180     m_repColIdx = xsh->getFirstFreeColumnIndex();
1181     m_repFx     = static_cast<TZeraryColumnFx *>(m_repColumn->getFx());
1182   }
1183 
1184   FxCommandUndo::cloneGroupStack(fx, m_repFx.getPointer());
1185 
1186   // Store fx's input links (depending on m_repFx, they could be not matched
1187   // in the replacement)
1188   int p, ipCount = fx->getInputPortCount();
1189   for (p = 0; p != ipCount; ++p) {
1190     TFxPort *port = fx->getInputPort(p);
1191     if (TFx *inputFx = port->getFx())
1192       m_inputLinks.push_back(std::make_pair(p, inputFx));
1193   }
1194 
1195   // Store the fx's linked fx
1196   m_linkedFx = fx->getLinkedFx();
1197 }
1198 
1199 //-------------------------------------------------------------
1200 
replace(TXsheet * xsh,TFx * fx,TFx * repFx,TXshColumn * column,TXshColumn * repColumn,int colIdx,int repColIdx)1201 void ReplaceFxUndo::replace(TXsheet *xsh, TFx *fx, TFx *repFx,
1202                             TXshColumn *column, TXshColumn *repColumn,
1203                             int colIdx, int repColIdx) {
1204   FxDag *fxDag = xsh->getFxDag();
1205 
1206   TZeraryColumnFx *zcfx = column ? static_cast<TZeraryColumnFx *>(fx) : 0;
1207   TZeraryColumnFx *repZcfx =
1208       repColumn ? static_cast<TZeraryColumnFx *>(repFx) : 0;
1209 
1210   TFx *ifx    = zcfx ? zcfx->getZeraryFx() : fx;
1211   TFx *irepFx = repZcfx ? repZcfx->getZeraryFx() : repFx;
1212 
1213   // Copy links first
1214   int p, ipCount = ifx->getInputPortCount(),
1215          ripCount = irepFx->getInputPortCount();
1216   for (p = 0; p != ipCount && p != ripCount; ++p) {
1217     TFxPort *ifxPort    = ifx->getInputPort(p);
1218     TFxPort *irepFxPort = irepFx->getInputPort(p);
1219 
1220     FxCommandUndo::attach(xsh, ifxPort->getFx(), irepFx, p, true);
1221   }
1222 
1223   int opCount = fx->getOutputConnectionCount();
1224   for (p = opCount - 1; p >= 0; --p) {
1225     TFxPort *port = fx->getOutputConnection(p);
1226     port->setFx(repFx);
1227   }
1228 
1229   if (fxDag->getTerminalFxs()->containsFx(fx)) {
1230     fxDag->removeFromXsheet(fx);
1231     fxDag->addToXsheet(repFx);
1232   }
1233 
1234   // Remove fx/column
1235   FxCommandUndo::removeFxOrColumn(xsh, fx, colIdx, bool(repColumn), false);
1236 
1237   // Insert the new fx/column
1238   if (repColumn)
1239     FxCommandUndo::insertColumn(xsh, repColumn, repColIdx,
1240                                 column);  // Not attached to the xsheet
1241   else
1242     ::addFxToCurrentScene(repFx, xsh, false);
1243 
1244   FxCommandUndo::copyGroupEditLevel(fx, repFx);
1245   FxCommandUndo::copyDagPosition(fx, repFx);
1246 }
1247 
1248 //-------------------------------------------------------------
1249 
redo() const1250 void ReplaceFxUndo::redo() const {
1251   ReplaceFxUndo::replace(m_xshHandle->getXsheet(), m_fx.getPointer(),
1252                          m_repFx.getPointer(), m_column.getPointer(),
1253                          m_repColumn.getPointer(), m_colIdx, m_repColIdx);
1254   FxCommandUndo::unlinkParams(m_fx.getPointer());
1255 
1256   m_fxHandle->setFx(0);
1257   m_xshHandle->notifyXsheetChanged();
1258 }
1259 
1260 //-------------------------------------------------------------
1261 
undo() const1262 void ReplaceFxUndo::undo() const {
1263   ReplaceFxUndo::replace(m_xshHandle->getXsheet(), m_repFx.getPointer(),
1264                          m_fx.getPointer(), m_repColumn.getPointer(),
1265                          m_column.getPointer(), m_repColIdx, m_colIdx);
1266 
1267   // Repair original input links for m_fx
1268   m_fx->disconnectAll();
1269 
1270   size_t l, lCount = m_inputLinks.size();
1271   for (l = 0; l != lCount; ++l)
1272     m_fx->getInputPort(m_inputLinks[l].first)->setFx(m_inputLinks[l].second);
1273 
1274   // Repair parameter links
1275   FxCommandUndo::linkParams(m_fx.getPointer(), m_linkedFx.getPointer());
1276 
1277   m_fxHandle->setFx(0);
1278   m_xshHandle->notifyXsheetChanged();
1279 }
1280 
1281 //-------------------------------------------------------------
1282 
getHistoryString()1283 QString ReplaceFxUndo::getHistoryString() {
1284   QString str = QObject::tr("Replace Fx  : ");
1285   str += QString("%1 > %2")
1286              .arg(QString::fromStdWString(m_fx->getFxId()))
1287              .arg(QString::fromStdWString(m_repFx->getFxId()));
1288 
1289   return str;
1290 }
1291 
1292 //=============================================================
1293 
replaceFx(TFx * newFx,const QList<TFxP> & fxs,TXsheetHandle * xshHandle,TFxHandle * fxHandle)1294 void TFxCommand::replaceFx(TFx *newFx, const QList<TFxP> &fxs,
1295                            TXsheetHandle *xshHandle, TFxHandle *fxHandle) {
1296   if (!newFx) return;
1297 
1298   TUndoManager *undoManager = TUndoManager::manager();
1299   ::CloneFxFunctor cloneFx  = {newFx, true};
1300 
1301   undoManager->beginBlock();
1302 
1303   TFxP clonedFx;
1304 
1305   int f, fCount = fxs.size();
1306   for (f = 0; f != fCount; ++f) {
1307     if (!clonedFx) clonedFx = cloneFx();
1308 
1309     std::unique_ptr<FxCommandUndo> undo(
1310         new ReplaceFxUndo(clonedFx, fxs[f], xshHandle, fxHandle));
1311     if (undo->isConsistent()) {
1312       undo->redo();
1313       undoManager->add(undo.release());
1314 
1315       clonedFx = TFxP();
1316     }
1317   }
1318 
1319   undoManager->endBlock();
1320 }
1321 
1322 //**********************************************************************
1323 //    Unlink Fx  command
1324 //**********************************************************************
1325 
1326 class UnlinkFxUndo final : public FxCommandUndo {
1327   TFxP m_fx, m_linkedFx;
1328 
1329   TXsheetHandle *m_xshHandle;
1330 
1331 public:
UnlinkFxUndo(const TFxP & fx,TXsheetHandle * xshHandle)1332   UnlinkFxUndo(const TFxP &fx, TXsheetHandle *xshHandle)
1333       : m_fx(fx), m_linkedFx(fx->getLinkedFx()), m_xshHandle(xshHandle) {}
1334 
isConsistent() const1335   bool isConsistent() const override { return bool(m_linkedFx); }
1336 
undo() const1337   void undo() const override {
1338     FxCommandUndo::linkParams(m_fx.getPointer(), m_linkedFx.getPointer());
1339     m_xshHandle->notifyXsheetChanged();
1340   }
1341 
redo() const1342   void redo() const override {
1343     FxCommandUndo::unlinkParams(m_fx.getPointer());
1344     m_xshHandle->notifyXsheetChanged();
1345   }
1346 
getSize() const1347   int getSize() const override { return sizeof(*this); }
1348 
getHistoryString()1349   QString getHistoryString() override {
1350     return QObject::tr("Unlink Fx  : %1 - - %2")
1351         .arg(QString::fromStdWString(m_fx->getFxId()))
1352         .arg(QString::fromStdWString(m_linkedFx->getFxId()));
1353   }
1354 };
1355 
1356 //=============================================================
1357 
unlinkFx(TFx * fx,TFxHandle * fxHandle,TXsheetHandle * xshHandle)1358 void TFxCommand::unlinkFx(TFx *fx, TFxHandle *fxHandle,
1359                           TXsheetHandle *xshHandle) {
1360   if (!fx) return;
1361 
1362   std::unique_ptr<FxCommandUndo> undo(new UnlinkFxUndo(fx, xshHandle));
1363   if (undo->isConsistent()) {
1364     undo->redo();
1365     TUndoManager::manager()->add(undo.release());
1366   }
1367 }
1368 
1369 //**********************************************************************
1370 //    Make Macro Fx  command
1371 //**********************************************************************
1372 
1373 class MakeMacroUndo : public FxCommandUndo {
1374 protected:
1375   TFxP m_macroFx;
1376   TApplication *m_app;
1377 
1378 public:
MakeMacroUndo(const std::vector<TFxP> & fxs,TApplication * app)1379   MakeMacroUndo(const std::vector<TFxP> &fxs, TApplication *app) : m_app(app) {
1380     initialize(fxs);
1381   }
1382 
isConsistent() const1383   bool isConsistent() const override { return bool(m_macroFx); }
1384 
1385   void redo() const override;
1386   void undo() const override;
1387 
getSize() const1388   int getSize() const override { return sizeof(*this); }
1389 
getHistoryString()1390   QString getHistoryString() override {
1391     return QObject::tr("Make Macro Fx  : %1")
1392         .arg(QString::fromStdWString(m_macroFx->getFxId()));
1393   }
1394 
1395 private:
1396   void initialize(const std::vector<TFxP> &fxs);
1397 
1398 protected:
MakeMacroUndo(TMacroFx * macroFx,TApplication * app)1399   MakeMacroUndo(TMacroFx *macroFx, TApplication *app)
1400       : m_macroFx(macroFx), m_app(app) {}
1401 };
1402 
1403 //-------------------------------------------------------------
1404 
initialize(const std::vector<TFxP> & fxs)1405 void MakeMacroUndo::initialize(const std::vector<TFxP> &fxs) {
1406   TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
1407 
1408   size_t f, fCount = fxs.size();
1409   for (f = 0; f != fCount; ++f) {
1410     // Only normal Fxs can be added in a macro
1411     TFx *fx = fxs[f].getPointer();
1412 
1413     if (isInsideAMacroFx(fx, xsh) || fx->isZerary() ||
1414         dynamic_cast<TZeraryColumnFx *>(fx) || dynamic_cast<TMacroFx *>(fx) ||
1415         dynamic_cast<TLevelColumnFx *>(fx) ||
1416         dynamic_cast<TPaletteColumnFx *>(fx) || dynamic_cast<TXsheetFx *>(fx) ||
1417         dynamic_cast<TOutputFx *>(fx))
1418       return;
1419   }
1420 
1421   TMacroFx *macroFx = TMacroFx::create(fxs);
1422   if (!macroFx) return;
1423 
1424   ::initializeFx(xsh, macroFx);
1425   m_macroFx = TFxP(macroFx);
1426 
1427   // An old comment suggested there may be trouble in case the fx editor popup
1428   // is opened.
1429   // In any case, the new macro fx will be selected at the end - so, let's
1430   // disable it right now
1431   m_app->getCurrentFx()->setFx(0);
1432 }
1433 
1434 //-------------------------------------------------------------
1435 
redo() const1436 void MakeMacroUndo::redo() const {
1437   TXsheet *xsh        = m_app->getCurrentXsheet()->getXsheet();
1438   FxDag *fxDag        = xsh->getFxDag();
1439   TFxSet *terminalFxs = fxDag->getTerminalFxs();
1440   TMacroFx *macroFx   = static_cast<TMacroFx *>(m_macroFx.getPointer());
1441 
1442   ::addFxToCurrentScene(macroFx, xsh, false);
1443 
1444   // Replace the macro's root and deal with output links
1445   TFx *rootFx = macroFx->getRoot();
1446   if (terminalFxs->containsFx(rootFx)) fxDag->addToXsheet(macroFx);
1447 
1448   int p, opCount = rootFx->getOutputConnectionCount();
1449   for (p = opCount - 1; p >= 0; --p)
1450     rootFx->getOutputConnection(p)->setFx(macroFx);
1451 
1452   // Remove the macro's internal fxs from the scene
1453   const std::vector<TFxP> &fxs = macroFx->getFxs();
1454 
1455   size_t f, fCount = fxs.size();
1456   for (f = 0; f != fCount; ++f)
1457     ::removeFxFromCurrentScene(fxs[f].getPointer(), xsh);
1458 
1459   // Hijack their ports (no actual redirection) - resetting the port ownership.
1460   // NOTE: Is this even legal? Not gonna touch it, but...   o_o!
1461   int ipCount = macroFx->getInputPortCount();
1462   for (p = 0; p != ipCount; ++p) macroFx->getInputPort(p)->setOwnerFx(macroFx);
1463 
1464   m_app->getCurrentFx()->setFx(macroFx);
1465   m_app->getCurrentXsheet()->notifyXsheetChanged();
1466 }
1467 
1468 //-------------------------------------------------------------
1469 
undo() const1470 void MakeMacroUndo::undo() const {
1471   TXsheet *xsh        = m_app->getCurrentXsheet()->getXsheet();
1472   FxDag *fxDag        = xsh->getFxDag();
1473   TFxSet *terminalFxs = fxDag->getTerminalFxs();
1474   TMacroFx *macroFx   = static_cast<TMacroFx *>(m_macroFx.getPointer());
1475 
1476   // Reattach the macro's root to the xsheet if necessary
1477   TFx *rootFx = macroFx->getRoot();
1478   if (terminalFxs->containsFx(macroFx)) fxDag->addToXsheet(rootFx);
1479 
1480   // Restore the root's output connections
1481   int p, opCount = macroFx->getOutputConnectionCount();
1482   for (p = opCount - 1; p >= 0; --p)
1483     macroFx->getOutputConnection(p)->setFx(rootFx);
1484 
1485   // Remove the macro
1486   ::removeFxFromCurrentScene(macroFx, xsh);
1487 
1488   // Re-insert the macro's internal fxs and restore ports ownership
1489   const std::vector<TFxP> &fxs = macroFx->getFxs();
1490 
1491   size_t f, fCount = fxs.size();
1492   for (f = 0; f != fCount; ++f) {
1493     TFx *fx = fxs[f].getPointer();
1494 
1495     ::addFxToCurrentScene(fx, xsh, false);
1496 
1497     int p, ipCount = fx->getInputPortCount();
1498     for (p = 0; p != ipCount; ++p) fx->getInputPort(p)->setOwnerFx(fx);
1499   }
1500 
1501   m_app->getCurrentFx()->setFx(0);
1502   m_app->getCurrentXsheet()->notifyXsheetChanged();
1503 }
1504 
1505 //=============================================================
1506 
makeMacroFx(const std::vector<TFxP> & fxs,TApplication * app)1507 void TFxCommand::makeMacroFx(const std::vector<TFxP> &fxs, TApplication *app) {
1508   if (fxs.empty()) return;
1509 
1510   std::unique_ptr<FxCommandUndo> undo(new MakeMacroUndo(fxs, app));
1511   if (undo->isConsistent()) {
1512     undo->redo();
1513     TUndoManager::manager()->add(undo.release());
1514   }
1515 }
1516 
1517 //**********************************************************************
1518 //    Explode Macro Fx  command
1519 //**********************************************************************
1520 
1521 class ExplodeMacroUndo final : public MakeMacroUndo {
1522 public:
ExplodeMacroUndo(TMacroFx * macro,TApplication * app)1523   ExplodeMacroUndo(TMacroFx *macro, TApplication *app)
1524       : MakeMacroUndo(macro, app) {
1525     initialize();
1526   }
1527 
redo() const1528   void redo() const override { MakeMacroUndo::undo(); }
undo() const1529   void undo() const override { MakeMacroUndo::redo(); }
1530 
getSize() const1531   int getSize() const override { return sizeof(*this); }
1532 
getHistoryString()1533   QString getHistoryString() override {
1534     return QObject::tr("Explode Macro Fx  : %1")
1535         .arg(QString::fromStdWString(m_macroFx->getFxId()));
1536   }
1537 
1538 private:
1539   void initialize();
1540 };
1541 
1542 //------------------------------------------------------
1543 
initialize()1544 void ExplodeMacroUndo::initialize() {
1545   if (!static_cast<TMacroFx *>(m_macroFx.getPointer())->getRoot())
1546     m_macroFx = TFxP();
1547 }
1548 
1549 //=============================================================
1550 
explodeMacroFx(TMacroFx * macroFx,TApplication * app)1551 void TFxCommand::explodeMacroFx(TMacroFx *macroFx, TApplication *app) {
1552   if (!macroFx) return;
1553 
1554   std::unique_ptr<FxCommandUndo> undo(new ExplodeMacroUndo(macroFx, app));
1555   if (undo->isConsistent()) {
1556     undo->redo();
1557     TUndoManager::manager()->add(undo.release());
1558   }
1559 }
1560 
1561 //**********************************************************************
1562 //    Create Output Fx  command
1563 //**********************************************************************
1564 
1565 class CreateOutputFxUndo final : public FxCommandUndo {
1566   TFxP m_outputFx;
1567   TXsheetHandle *m_xshHandle;
1568 
1569 public:
CreateOutputFxUndo(TFx * fx,TXsheetHandle * xshHandle)1570   CreateOutputFxUndo(TFx *fx, TXsheetHandle *xshHandle)
1571       : m_outputFx(new TOutputFx), m_xshHandle(xshHandle) {
1572     initialize(fx);
1573   }
1574 
isConsistent() const1575   bool isConsistent() const override { return true; }
1576 
redo() const1577   void redo() const override {
1578     FxDag *fxDag        = m_xshHandle->getXsheet()->getFxDag();
1579     TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());
1580 
1581     fxDag->addOutputFx(outputFx);
1582     fxDag->setCurrentOutputFx(outputFx);
1583 
1584     m_xshHandle->notifyXsheetChanged();
1585   }
1586 
undo() const1587   void undo() const override {
1588     TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());
1589 
1590     m_xshHandle->getXsheet()->getFxDag()->removeOutputFx(outputFx);
1591     m_xshHandle->notifyXsheetChanged();
1592   }
1593 
getSize() const1594   int getSize() const override { return sizeof(*this); }
1595 
getHistoryString()1596   QString getHistoryString() override {
1597     return QObject::tr("Create Output Fx");
1598   }
1599 
1600 private:
initialize(TFx * fx)1601   void initialize(TFx *fx) {
1602     TXsheet *xsh        = m_xshHandle->getXsheet();
1603     TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());
1604 
1605     if (fx && !dynamic_cast<TOutputFx *>(fx))
1606       outputFx->getInputPort(0)->setFx(fx);
1607     else {
1608       TOutputFx *currentOutputFx = xsh->getFxDag()->getCurrentOutputFx();
1609       const TPointD &pos = currentOutputFx->getAttributes()->getDagNodePos();
1610       if (pos != TConst::nowhere)
1611         outputFx->getAttributes()->setDagNodePos(pos + TPointD(20, 20));
1612     }
1613   }
1614 };
1615 
1616 //=============================================================
1617 
createOutputFx(TXsheetHandle * xshHandle,TFx * currentFx)1618 void TFxCommand::createOutputFx(TXsheetHandle *xshHandle, TFx *currentFx) {
1619   TUndo *undo = new CreateOutputFxUndo(currentFx, xshHandle);
1620 
1621   undo->redo();
1622   TUndoManager::manager()->add(undo);
1623 }
1624 
1625 //**********************************************************************
1626 //    Make Output Fx Current  command
1627 //**********************************************************************
1628 
makeOutputFxCurrent(TFx * fx,TXsheetHandle * xshHandle)1629 void TFxCommand::makeOutputFxCurrent(TFx *fx, TXsheetHandle *xshHandle) {
1630   TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx);
1631   if (!outputFx) return;
1632 
1633   TXsheet *xsh = xshHandle->getXsheet();
1634   if (xsh->getFxDag()->getCurrentOutputFx() == outputFx) return;
1635 
1636   xsh->getFxDag()->setCurrentOutputFx(outputFx);
1637   xshHandle->notifyXsheetChanged();
1638 }
1639 
1640 //**********************************************************************
1641 //    Connect Nodes To Xsheet  command
1642 //**********************************************************************
1643 
1644 class ConnectNodesToXsheetUndo : public FxCommandUndo {
1645 protected:
1646   std::vector<TFxP> m_fxs;
1647   TXsheetHandle *m_xshHandle;
1648 
1649 public:
ConnectNodesToXsheetUndo(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)1650   ConnectNodesToXsheetUndo(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
1651       : m_fxs(fxs.begin(), fxs.end()), m_xshHandle(xshHandle) {
1652     initialize();
1653   }
1654 
isConsistent() const1655   bool isConsistent() const override { return !m_fxs.empty(); }
1656 
redo() const1657   void redo() const override {
1658     /*
1659 Due to compatibility issues from *schematicnode.cpp files, the "do" operation
1660 must be
1661 accessible without scene change notifications (see TFxCommand::setParent())
1662 */
1663 
1664     redo_();
1665     m_xshHandle->notifyXsheetChanged();
1666   }
1667 
redo_() const1668   void redo_() const {
1669     FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();
1670 
1671     size_t f, fCount = m_fxs.size();
1672     for (f = 0; f != fCount; ++f) fxDag->addToXsheet(m_fxs[f].getPointer());
1673   }
1674 
undo() const1675   void undo() const override {
1676     FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();
1677 
1678     size_t f, fCount = m_fxs.size();
1679     for (f = 0; f != fCount; ++f)
1680       fxDag->removeFromXsheet(m_fxs[f].getPointer());
1681 
1682     m_xshHandle->notifyXsheetChanged();
1683   }
1684 
getSize() const1685   int getSize() const override { return sizeof(*this); }
1686 
getHistoryString()1687   QString getHistoryString() override {
1688     QString str = QObject::tr("Connect to Xsheet  : ");
1689     std::vector<TFxP>::iterator it;
1690     for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
1691       if (it != m_fxs.begin()) str += QString(", ");
1692       str += QString::fromStdWString((*it)->getFxId());
1693     }
1694     return str;
1695   }
1696 
1697 protected:
ConnectNodesToXsheetUndo(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle,bool)1698   ConnectNodesToXsheetUndo(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle,
1699                            bool)
1700       : m_fxs(fxs.begin(), fxs.end()), m_xshHandle(xshHandle) {}
1701 
1702 private:
1703   void initialize();
1704 };
1705 
1706 //------------------------------------------------------
1707 
initialize()1708 void ConnectNodesToXsheetUndo::initialize() {
1709   TXsheet *xsh = m_xshHandle->getXsheet();
1710 
1711   ::FilterInsideAMacro filterInMacro = {xsh};
1712   m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterInMacro),
1713               m_fxs.end());
1714 
1715   ::FilterTerminalFxs filterTerminalFxs = {xsh};
1716   m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterTerminalFxs),
1717               m_fxs.end());
1718 }
1719 
1720 //=============================================================
1721 
connectNodesToXsheet(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)1722 void TFxCommand::connectNodesToXsheet(const std::list<TFxP> &fxs,
1723                                       TXsheetHandle *xshHandle) {
1724   std::unique_ptr<FxCommandUndo> undo(
1725       new ConnectNodesToXsheetUndo(fxs, xshHandle));
1726   if (undo->isConsistent()) {
1727     undo->redo();
1728     TUndoManager::manager()->add(undo.release());
1729   }
1730 }
1731 
1732 //**********************************************************************
1733 //    Disconnect Nodes From Xsheet  command
1734 //**********************************************************************
1735 
1736 class DisconnectNodesFromXsheetUndo final : public ConnectNodesToXsheetUndo {
1737 public:
DisconnectNodesFromXsheetUndo(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)1738   DisconnectNodesFromXsheetUndo(const std::list<TFxP> &fxs,
1739                                 TXsheetHandle *xshHandle)
1740       : ConnectNodesToXsheetUndo(fxs, xshHandle, true) {
1741     initialize();
1742   }
1743 
redo() const1744   void redo() const override { ConnectNodesToXsheetUndo::undo(); }
undo() const1745   void undo() const override { ConnectNodesToXsheetUndo::redo(); }
1746 
getHistoryString()1747   QString getHistoryString() override {
1748     QString str = QObject::tr("Disconnect from Xsheet  : ");
1749     std::vector<TFxP>::iterator it;
1750     for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
1751       if (it != m_fxs.begin()) str += QString(", ");
1752       str += QString::fromStdWString((*it)->getFxId());
1753     }
1754     return str;
1755   }
1756 
1757 private:
1758   void initialize();
1759 };
1760 
1761 //------------------------------------------------------
1762 
initialize()1763 void DisconnectNodesFromXsheetUndo::initialize() {
1764   TXsheet *xsh = m_xshHandle->getXsheet();
1765 
1766   ::FilterInsideAMacro filterInMacro = {xsh};
1767   m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterInMacro),
1768               m_fxs.end());
1769 
1770   ::FilterNonTerminalFxs filterNonTerminalFxs = {xsh};
1771   m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterNonTerminalFxs),
1772               m_fxs.end());
1773 }
1774 
1775 //=============================================================
1776 
disconnectNodesFromXsheet(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)1777 void TFxCommand::disconnectNodesFromXsheet(const std::list<TFxP> &fxs,
1778                                            TXsheetHandle *xshHandle) {
1779   std::unique_ptr<FxCommandUndo> undo(
1780       new DisconnectNodesFromXsheetUndo(fxs, xshHandle));
1781   if (undo->isConsistent()) {
1782     undo->redo();
1783     TUndoManager::manager()->add(undo.release());
1784   }
1785 }
1786 
1787 //**********************************************************************
1788 //    Delete Link  command
1789 //**********************************************************************
1790 
1791 class DeleteLinksUndo : public FxCommandUndo {
1792   struct DynamicLink {
1793     int m_groupIndex;
1794     std::string m_portName;
1795     TFx *m_inputFx;
1796   };
1797 
1798   typedef std::vector<DynamicLink> DynamicLinksVector;
1799 
1800 protected:
1801   std::list<TFxCommand::Link> m_links;  //!< The input links to remove
1802 
1803 private:
1804   std::list<TFxCommand::Link>
1805       m_normalLinks;               //!< Actual *common* links from m_links
1806   std::list<TFx *> m_terminalFxs;  //!< Fxs connected to the xsheet (represents
1807                                    //! xsheet input links)
1808   //   Why SMART pointers? No fx is deleted with this command... hmm...
1809   std::map<TFx *, DynamicLinksVector>
1810       m_dynamicLinks;  //!< Complete dynamic links configuration, per fx.
1811 
1812   TXsheetHandle *m_xshHandle;
1813 
1814 public:
DeleteLinksUndo(const std::list<TFxCommand::Link> & links,TXsheetHandle * xshHandle)1815   DeleteLinksUndo(const std::list<TFxCommand::Link> &links,
1816                   TXsheetHandle *xshHandle)
1817       : m_links(links), m_xshHandle(xshHandle) {
1818     initialize();
1819   }
1820 
isConsistent() const1821   bool isConsistent() const override { return !m_links.empty(); }
1822 
1823   void redo() const override;
1824   void undo() const override;
1825 
getSize() const1826   int getSize() const override { return 10 << 10; }  // Say, around 10 kB
1827 
1828   QString getHistoryString() override;
1829 
1830 protected:
DeleteLinksUndo(TXsheetHandle * xshHandle)1831   DeleteLinksUndo(TXsheetHandle *xshHandle) : m_xshHandle(xshHandle) {}
1832 
1833   void initialize();
1834 };
1835 
1836 //------------------------------------------------------
1837 
initialize()1838 void DeleteLinksUndo::initialize() {
1839   struct locals {
1840     static bool isInvalid(FxDag *fxDag, const TFxCommand::Link &link) {
1841       if (link.m_index < 0)
1842         return !fxDag->getTerminalFxs()->containsFx(
1843             link.m_inputFx.getPointer());
1844 
1845       TFx *inFx  = ::getActualOut(link.m_inputFx.getPointer());
1846       TFx *outFx = ::getActualIn(link.m_outputFx.getPointer());
1847 
1848       return (link.m_index >= outFx->getInputPortCount())
1849                  ? true
1850                  : (outFx->getInputPort(link.m_index)->getFx() != inFx);
1851     }
1852   };
1853 
1854   TXsheet *xsh = m_xshHandle->getXsheet();
1855   FxDag *fxDag = xsh->getFxDag();
1856 
1857   // Forget links dealing with an open macro Fx. Note that this INCLUDES
1858   // inside/outside links.
1859   ::FilterInsideAMacro filterInMacro = {xsh};
1860   m_links.erase(std::remove_if(m_links.begin(), m_links.end(), filterInMacro),
1861                 m_links.end());
1862 
1863   // Remove invalid links
1864   m_links.erase(std::remove_if(m_links.begin(), m_links.end(),
1865                                [fxDag](const TFxCommand::Link &link) {
1866                                  return locals::isInvalid(fxDag, link);
1867                                }),
1868                 m_links.end());
1869 
1870   std::list<TFxCommand::Link>::iterator lt, lEnd(m_links.end());
1871   for (lt = m_links.begin(); lt != lEnd; ++lt) {
1872     const TFxCommand::Link &link = *lt;
1873 
1874     if (TXsheetFx *xsheetFx =
1875             dynamic_cast<TXsheetFx *>(link.m_outputFx.getPointer())) {
1876       // The input fx is connected to an xsheet node - ie it's terminal
1877       m_terminalFxs.push_back(link.m_inputFx.getPointer());
1878       continue;
1879     }
1880 
1881     TFx *outputFx = link.m_outputFx.getPointer();
1882 
1883     // Zerary columns wrap the actual zerary fx - that is the fx holding input
1884     // ports
1885     if (TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
1886       outputFx = zfx->getZeraryFx();
1887 
1888     TFxPort *port = outputFx->getInputPort(link.m_index);
1889 
1890     int portGroup = port->getGroupIndex();
1891     if (portGroup < 0) m_normalLinks.push_back(link);
1892 
1893     if (outputFx->hasDynamicPortGroups())
1894       m_dynamicLinks.insert(std::make_pair(outputFx, DynamicLinksVector()));
1895   }
1896 
1897   m_normalLinks.sort();  // Really necessary?
1898 
1899   // Store the complete configuration of dynamic groups - not just the ones
1900   // touched by
1901   // link editing, ALL of them.
1902   std::map<TFx *, DynamicLinksVector>::iterator dlt,
1903       dlEnd(m_dynamicLinks.end());
1904   for (dlt = m_dynamicLinks.begin(); dlt != dlEnd; ++dlt) {
1905     TFx *outputFx                = dlt->first;
1906     DynamicLinksVector &dynLinks = dlt->second;
1907 
1908     int p, pCount = outputFx->getInputPortCount();
1909     for (p = 0; p != pCount; ++p) {
1910       TFxPort *port = outputFx->getInputPort(p);
1911 
1912       int g = port->getGroupIndex();
1913       if (g >= 0) {
1914         DynamicLink dLink = {g, outputFx->getInputPortName(p), port->getFx()};
1915         dynLinks.push_back(dLink);
1916       }
1917     }
1918   }
1919 }
1920 
1921 //------------------------------------------------------
1922 
redo() const1923 void DeleteLinksUndo::redo() const {
1924   FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();
1925 
1926   // Perform unlinking
1927   std::list<TFxCommand::Link>::const_iterator lt, lEnd(m_links.end());
1928   for (lt = m_links.begin(); lt != lEnd; ++lt) {
1929     const TFxCommand::Link &link = *lt;
1930 
1931     TFx *outputFx = lt->m_outputFx.getPointer();
1932 
1933     if (TXsheetFx *xsheetFx = dynamic_cast<TXsheetFx *>(outputFx)) {
1934       // Terminal fx link case
1935       fxDag->removeFromXsheet(link.m_inputFx.getPointer());
1936       continue;
1937     }
1938 
1939     // Actual link case
1940     if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
1941       outputFx = zcfx->getZeraryFx();
1942 
1943     int index = lt->m_index;
1944 
1945     assert(index < outputFx->getInputPortCount());
1946     if (index < outputFx->getInputPortCount())
1947       outputFx->getInputPort(index)->setFx(0);
1948   }
1949 
1950   if (m_isLastInRedoBlock) m_xshHandle->notifyXsheetChanged();
1951 }
1952 
1953 //------------------------------------------------------
1954 
undo() const1955 void DeleteLinksUndo::undo() const {
1956   FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();
1957 
1958   // Re-attach terminal fxs
1959   std::list<TFx *>::const_iterator ft;
1960   for (ft = m_terminalFxs.begin(); ft != m_terminalFxs.end(); ++ft) {
1961     if (fxDag->checkLoop(*ft, fxDag->getXsheetFx())) {
1962       assert(fxDag->checkLoop(*ft, fxDag->getXsheetFx()));
1963       continue;
1964     }
1965 
1966     fxDag->addToXsheet(*ft);
1967   }
1968 
1969   // Restore common links
1970   std::list<TFxCommand::Link>::const_iterator lt, lEnd(m_normalLinks.end());
1971   for (lt = m_normalLinks.begin(); lt != lEnd; ++lt) {
1972     const TFxCommand::Link &link = *lt;
1973 
1974     int index     = link.m_index;
1975     TFx *inputFx  = link.m_inputFx.getPointer();
1976     TFx *outputFx = link.m_outputFx.getPointer();
1977 
1978     if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
1979       outputFx = zcfx->getZeraryFx();
1980 
1981     if (fxDag->checkLoop(inputFx, outputFx)) {
1982       assert(fxDag->checkLoop(inputFx, outputFx));
1983       continue;
1984     }
1985 
1986     assert(index < outputFx->getInputPortCount());
1987 
1988     if (index < outputFx->getInputPortCount())
1989       outputFx->getInputPort(index)->setFx(inputFx);
1990   }
1991 
1992   // Restore complete dynamic port groups configuration
1993   std::map<TFx *, DynamicLinksVector>::const_iterator dlt,
1994       dlEnd(m_dynamicLinks.end());
1995   for (dlt = m_dynamicLinks.begin(); dlt != dlEnd; ++dlt) {
1996     TFx *outputFx                      = dlt->first;
1997     const DynamicLinksVector &dynLinks = dlt->second;
1998 
1999     {
2000       int g, gCount = outputFx->dynamicPortGroupsCount();
2001       for (g = 0; g != gCount; ++g) outputFx->clearDynamicPortGroup(g);
2002     }
2003 
2004     size_t d, dCount = dynLinks.size();
2005     for (d = 0; d != dCount; ++d) {
2006       const DynamicLink &link = dynLinks[d];
2007 
2008       TFxPort *port = new TRasterFxPort;  // isAControlPort... semi-obsolete
2009       port->setFx(link.m_inputFx);
2010 
2011       outputFx->addInputPort(link.m_portName, port, link.m_groupIndex);
2012     }
2013   }
2014 
2015   if (m_isLastInBlock) m_xshHandle->notifyXsheetChanged();
2016 }
2017 
2018 //------------------------------------------------------
2019 
getHistoryString()2020 QString DeleteLinksUndo::getHistoryString() {
2021   QString str = QObject::tr("Delete Link");
2022   if (!m_normalLinks.empty()) {
2023     str += QString("  :  ");
2024     std::list<TFxCommand::Link>::const_iterator it;
2025     for (it = m_normalLinks.begin(); it != m_normalLinks.end(); it++) {
2026       if (it != m_normalLinks.begin()) str += QString(",  ");
2027       TFxCommand::Link boundingFxs = *it;
2028       str +=
2029           QString("%1- -%2")
2030               .arg(QString::fromStdWString(boundingFxs.m_inputFx->getName()))
2031               .arg(QString::fromStdWString(boundingFxs.m_outputFx->getName()));
2032     }
2033   }
2034 
2035   if (!m_terminalFxs.empty()) {
2036     str += QString("  :  ");
2037     std::list<TFx *>::const_iterator ft;
2038     for (ft = m_terminalFxs.begin(); ft != m_terminalFxs.end(); ++ft) {
2039       if (ft != m_terminalFxs.begin()) str += QString(",  ");
2040       str +=
2041           QString("%1- -Xsheet").arg(QString::fromStdWString((*ft)->getName()));
2042     }
2043   }
2044 
2045   return str;
2046 }
2047 
2048 //=============================================================
2049 
deleteLinks(const std::list<TFxCommand::Link> & links,TXsheetHandle * xshHandle)2050 static void deleteLinks(const std::list<TFxCommand::Link> &links,
2051                         TXsheetHandle *xshHandle) {
2052   std::unique_ptr<FxCommandUndo> undo(new DeleteLinksUndo(links, xshHandle));
2053   if (undo->isConsistent()) {
2054     undo->m_isLastInRedoBlock = false;
2055     undo->redo();
2056     TUndoManager::manager()->add(undo.release());
2057   }
2058 }
2059 
2060 //******************************************************
2061 //    Delete Fx  command
2062 //******************************************************
2063 
2064 class DeleteFxOrColumnUndo final : public DeleteLinksUndo {
2065 protected:
2066   TFxP m_fx;
2067   TXshColumnP m_column;
2068   int m_colIdx;
2069 
2070   TFxP m_linkedFx;
2071   std::vector<TFx *> m_nonTerminalInputs;
2072 
2073   mutable std::unique_ptr<TStageObjectParams> m_columnData;
2074 
2075   TXsheetHandle *m_xshHandle;
2076   TFxHandle *m_fxHandle;
2077 
2078   using DeleteLinksUndo::m_links;
2079 
2080 public:
2081   DeleteFxOrColumnUndo(const TFxP &fx, TXsheetHandle *xshHandle,
2082                        TFxHandle *fxHandle);
2083   DeleteFxOrColumnUndo(int colIdx, TXsheetHandle *xshHandle,
2084                        TFxHandle *fxHandle);
2085 
2086   bool isConsistent() const override;
2087 
2088   void redo() const override;
2089   void undo() const override;
2090 
getSize() const2091   int getSize() const override { return sizeof(*this); }
2092 
2093   QString getHistoryString() override;
2094 
2095 private:
2096   void initialize();
2097 };
2098 
2099 //-------------------------------------------------------------
2100 
DeleteFxOrColumnUndo(const TFxP & fx,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2101 DeleteFxOrColumnUndo::DeleteFxOrColumnUndo(const TFxP &fx,
2102                                            TXsheetHandle *xshHandle,
2103                                            TFxHandle *fxHandle)
2104     : DeleteLinksUndo(xshHandle)
2105     , m_fx(fx)
2106     , m_colIdx(-1)
2107     , m_xshHandle(xshHandle)
2108     , m_fxHandle(fxHandle) {
2109   initialize();
2110 }
2111 
2112 //-------------------------------------------------------------
2113 
DeleteFxOrColumnUndo(int colIdx,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2114 DeleteFxOrColumnUndo::DeleteFxOrColumnUndo(int colIdx, TXsheetHandle *xshHandle,
2115                                            TFxHandle *fxHandle)
2116     : DeleteLinksUndo(xshHandle)
2117     , m_colIdx(colIdx)
2118     , m_xshHandle(xshHandle)
2119     , m_fxHandle(fxHandle) {
2120   initialize();
2121 }
2122 
2123 //-------------------------------------------------------------
2124 
initialize()2125 void DeleteFxOrColumnUndo::initialize() {
2126   struct {
2127     DeleteFxOrColumnUndo *m_this;
2128     inline void getActualData(TXsheet *xsh, TFx *&ifx, TFx *&ofx) {
2129       TFx *fx = m_this->m_fx.getPointer();
2130 
2131       if (!fx) fx = xsh->getColumn(m_this->m_colIdx)->getFx();
2132 
2133       if (fx) {
2134         ifx = ::getActualIn(fx);
2135         ofx = ::getActualOut(fx);
2136 
2137         if (TColumnFx *colFx = dynamic_cast<TColumnFx *>(ofx))
2138           m_this->m_colIdx = colFx->getColumnIndex();
2139       }
2140 
2141       m_this->m_fx = ofx;
2142     }
2143   } locals = {this};
2144 
2145   assert(m_fx || m_colIdx >= 0);
2146 
2147   TXsheet *xsh = m_xshHandle->getXsheet();
2148   TFx *ifx = 0, *ofx = 0;
2149 
2150   locals.getActualData(xsh, ifx, ofx);
2151 
2152   if (ofx && isInsideAMacroFx(ofx, xsh))  // Macros must be exploded first
2153   {
2154     m_fx = TFxP(), m_colIdx = -1;
2155     return;
2156   }
2157 
2158   // Assume shared ownership of the associated column, if any
2159   if (m_colIdx >= 0) {
2160     m_column = xsh->getColumn(m_colIdx);
2161     assert(m_column);
2162 
2163     // Currently disputed, but in the previous implementation there was code
2164     // suggesting
2165     // that the column could have already been removed from the xsheet.
2166     // Preventing that case...
2167     if (!m_column->inColumnsSet()) {
2168       m_fx = TFxP(), m_colIdx = -1;  // Bail out as inconsistent op
2169       return;                        //
2170     }
2171   } else if (TOutputFx *outputFx = dynamic_cast<TOutputFx *>(ofx)) {
2172     if (xsh->getFxDag()->getOutputFxCount() <= 1) {
2173       // Cannot delete the last output fx
2174       m_fx = TFxP();
2175       assert(m_colIdx < 0);
2176       return;
2177     }
2178   }
2179 
2180   // Store links to be re-established in the undo
2181   FxDag *fxDag = xsh->getFxDag();
2182 
2183   if (ofx) {
2184     // Store the terminal output link, if any
2185     if (fxDag->getTerminalFxs()->containsFx(ofx))
2186       m_links.push_back(TFxCommand::Link(ofx, fxDag->getXsheetFx(), -1));
2187 
2188     // Store output links
2189     int p, opCount = ofx->getOutputConnectionCount();
2190     for (p = 0; p != opCount; ++p) {
2191       if (TFx *outFx = ofx->getOutputConnection(p)->getOwnerFx()) {
2192         int ip, ipCount = outFx->getInputPortCount();
2193         for (ip = 0; ip != ipCount; ++ip)
2194           if (outFx->getInputPort(ip)->getFx() == ofx) break;
2195 
2196         assert(ip < ipCount);
2197         if (ip < ipCount) m_links.push_back(TFxCommand::Link(m_fx, outFx, ip));
2198       }
2199     }
2200   }
2201 
2202   if (ifx) {
2203     m_linkedFx = ifx->getLinkedFx();
2204 
2205     // Store input links
2206     int p, ipCount = ifx->getInputPortCount();
2207     for (p = 0; p != ipCount; ++p) {
2208       if (TFx *inputFx = ifx->getInputPort(p)->getFx()) {
2209         m_links.push_back(TFxCommand::Link(inputFx, m_fx, p));
2210         if (!fxDag->getTerminalFxs()->containsFx(
2211                 inputFx))  // Store input fxs which DID NOT have an
2212           m_nonTerminalInputs.push_back(
2213               inputFx);  // xsheet link before the deletion
2214       }
2215     }
2216   }
2217 
2218   DeleteLinksUndo::initialize();
2219 }
2220 
2221 //-------------------------------------------------------------
2222 
isConsistent() const2223 bool DeleteFxOrColumnUndo::isConsistent() const {
2224   return (bool(m_fx) || (m_colIdx >= 0));
2225 
2226   // NOTE: DeleteLinksUndo::isConsistent() is not checked.
2227   //       This is because there could be no link to remove, and yet
2228   //       the operation IS consistent.
2229 }
2230 
2231 //-------------------------------------------------------------
2232 
redo() const2233 void DeleteFxOrColumnUndo::redo() const {
2234   TXsheet *xsh = m_xshHandle->getXsheet();
2235 
2236   // Store data to be restored in the undo
2237   if (m_colIdx >= 0) {
2238     assert(!m_columnData.get());
2239 
2240     m_columnData.reset(
2241         xsh->getStageObject(TStageObjectId::ColumnId(
2242                                 m_colIdx))  // Cloned, ownership acquired
2243             ->getParams());  // However, params stored there are NOT cloned.
2244   }                          // This is fine since we're deleting the column...
2245 
2246   // Perform operation
2247   FxCommandUndo::removeFxOrColumn(xsh, m_fx.getPointer(), m_colIdx);
2248 
2249   if (m_isLastInRedoBlock)
2250     m_xshHandle->notifyXsheetChanged();  // Add the rest...
2251 }
2252 
2253 //-------------------------------------------------------------
2254 
undo() const2255 void DeleteFxOrColumnUndo::undo() const {
2256   struct Locals {
2257     const DeleteFxOrColumnUndo *m_this;
2258 
2259     void insertColumnIn(TXsheet *xsh) {
2260       m_this->insertColumn(xsh, m_this->m_column.getPointer(),
2261                            m_this->m_colIdx);
2262 
2263       // Restore column data
2264       TStageObject *sObj =
2265           xsh->getStageObject(TStageObjectId::ColumnId(m_this->m_colIdx));
2266       assert(sObj);
2267 
2268       sObj->assignParams(m_this->m_columnData.get(), false);
2269       m_this->m_columnData.reset();
2270     }
2271 
2272   } locals = {this};
2273 
2274   TXsheet *xsh = m_xshHandle->getXsheet();
2275   FxDag *fxDag = xsh->getFxDag();
2276 
2277   // Re-add the fx/column to the xsheet
2278   TFx *fx = m_fx.getPointer();
2279 
2280   if (m_column)
2281     locals.insertColumnIn(xsh);
2282   else if (TOutputFx *outFx = dynamic_cast<TOutputFx *>(fx))
2283     xsh->getFxDag()->addOutputFx(outFx);
2284   else
2285     addFxToCurrentScene(fx, xsh, false);
2286 
2287   if (fx) {
2288     // Remove xsheet connections that became terminal due to the fx
2289     // removal
2290     size_t ti, tiCount = m_nonTerminalInputs.size();
2291     for (ti = 0; ti != tiCount; ++ti)
2292       fxDag->removeFromXsheet(m_nonTerminalInputs[ti]);
2293 
2294     // Re-link parameters if necessary
2295     TFx *ifx = ::getActualIn(fx);
2296 
2297     if (m_linkedFx) ifx->linkParams(m_linkedFx.getPointer());
2298 
2299     // Re-establish fx links
2300     DeleteLinksUndo::undo();
2301   } else if (m_isLastInBlock)  // Already covered by DeleteLinksUndo::undo()
2302     m_xshHandle->notifyXsheetChanged();  // in the other branch
2303 }
2304 
2305 //-------------------------------------------------------------
2306 
getHistoryString()2307 QString DeleteFxOrColumnUndo::getHistoryString() {
2308   return QObject::tr("Delete Fx Node : %1")
2309       .arg(QString::fromStdWString(m_fx->getFxId()));
2310 }
2311 
2312 //=============================================================
2313 
deleteFxs(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2314 static void deleteFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle,
2315                       TFxHandle *fxHandle) {
2316   TUndoManager *undoManager = TUndoManager::manager();
2317   TXsheet *xsh              = xshHandle->getXsheet();
2318 
2319   undoManager->beginBlock();
2320 
2321   std::list<TFxP>::const_iterator ft, fEnd = fxs.end();
2322   for (ft = fxs.begin(); ft != fEnd; ++ft) {
2323     // Skip levels, as they are effectively supplied in here, AND in the
2324     // deleteColumns() branch.
2325     // This should NOT be performed here, though. TO BE MOVED TO deleteSelection
2326     // or ABOVE, if any.
2327     if (dynamic_cast<TLevelColumnFx *>(ft->getPointer())) continue;
2328 
2329     std::unique_ptr<FxCommandUndo> undo(
2330         new DeleteFxOrColumnUndo(*ft, xshHandle, fxHandle));
2331     if (undo->isConsistent()) {
2332       // prevent emiting xsheetChanged signal for every undos which will cause
2333       // multiple triggers of preview rendering
2334       undo->m_isLastInRedoBlock = false;
2335       undo->redo();
2336       TUndoManager::manager()->add(undo.release());
2337     }
2338   }
2339 
2340   undoManager->endBlock();
2341 }
2342 
2343 //**********************************************************************
2344 //    Remove Output Fx  command
2345 //**********************************************************************
2346 
removeOutputFx(TFx * fx,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2347 void TFxCommand::removeOutputFx(TFx *fx, TXsheetHandle *xshHandle,
2348                                 TFxHandle *fxHandle) {
2349   TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx);
2350   if (!outputFx) return;
2351 
2352   std::unique_ptr<FxCommandUndo> undo(
2353       new DeleteFxOrColumnUndo(fx, xshHandle, fxHandle));
2354   if (undo->isConsistent()) {
2355     undo->redo();
2356     TUndoManager::manager()->add(undo.release());
2357   }
2358 }
2359 
2360 //**********************************************************************
2361 //    Delete Columns  command
2362 //**********************************************************************
2363 
deleteColumns(const std::list<int> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2364 static void deleteColumns(const std::list<int> &columns,
2365                           TXsheetHandle *xshHandle, TFxHandle *fxHandle) {
2366   TUndoManager *undoManager = TUndoManager::manager();
2367 
2368   undoManager->beginBlock();
2369 
2370   // As columns are deleted, their index changes. So, the easiest workaround is
2371   // to address the
2372   // columns directly, and then their (updated) column index.
2373   TXsheet *xsh = xshHandle->getXsheet();
2374 
2375   std::vector<TXshColumn *> cols;
2376   for (auto const &c : columns) {
2377     cols.push_back(xsh->getColumn(c));
2378   }
2379 
2380   size_t c, cCount = cols.size();
2381   for (c = 0; c != cCount; ++c) {
2382     std::unique_ptr<FxCommandUndo> undo(
2383         new DeleteFxOrColumnUndo(cols[c]->getIndex(), xshHandle, fxHandle));
2384     if (undo->isConsistent()) {
2385       // prevent emiting xsheetChanged signal for every undos which will cause
2386       // multiple triggers of preview rendering
2387       undo->m_isLastInRedoBlock = false;
2388       undo->redo();
2389       undoManager->add(undo.release());
2390     }
2391   }
2392 
2393   undoManager->endBlock();
2394 }
2395 
2396 //**********************************************************************
2397 //    Delete Selection  command
2398 //**********************************************************************
2399 
deleteSelection(const std::list<TFxP> & fxs,const std::list<Link> & links,const std::list<int> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2400 void TFxCommand::deleteSelection(const std::list<TFxP> &fxs,
2401                                  const std::list<Link> &links,
2402                                  const std::list<int> &columns,
2403                                  TXsheetHandle *xshHandle,
2404                                  TFxHandle *fxHandle) {
2405   // Prepare selected fxs - column fxs would be done twice if the corresponding
2406   // columns have been supplied for deletion too
2407   ::FilterColumnFxs filterColumnFxs;
2408 
2409   std::list<TFxP> filteredFxs(fxs);
2410   filteredFxs.erase(
2411       std::remove_if(filteredFxs.begin(), filteredFxs.end(), filterColumnFxs),
2412       filteredFxs.end());
2413 
2414   // Perform deletions
2415   TUndoManager::manager()->beginBlock();
2416 
2417   if (!columns.empty()) deleteColumns(columns, xshHandle, fxHandle);
2418   if (!filteredFxs.empty()) deleteFxs(filteredFxs, xshHandle, fxHandle);
2419   if (!links.empty()) deleteLinks(links, xshHandle);
2420 
2421   TUndoManager::manager()->endBlock();
2422   // emit xsheetChanged once here
2423   xshHandle->notifyXsheetChanged();
2424 }
2425 
2426 //**********************************************************************
2427 //    Paste Fxs  command
2428 //**********************************************************************
2429 
2430 /*
2431   NOTE: Zerary fxs should not be in the fxs list - but rather in the columns
2432   list.
2433   This would allow us to forget about the zeraryColumnSize map.
2434 
2435   This requires changing the *selection*, though. To be done, in a later step.
2436 */
2437 
2438 class UndoPasteFxs : public FxCommandUndo {
2439 protected:
2440   std::list<TFxP> m_fxs;             //!< Fxs to be pasted
2441   std::list<TXshColumnP> m_columns;  //!< Columns to be pasted
2442   std::vector<TFxCommand::Link>
2443       m_links;  //!< Links to be re-established at the redo
2444 
2445   TXsheetHandle *m_xshHandle;
2446   TFxHandle *m_fxHandle;
2447 
2448 public:
UndoPasteFxs(const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,const TPointD & pos,TXsheetHandle * xshHandle,TFxHandle * fxHandle,bool addOffset=true)2449   UndoPasteFxs(const std::list<TFxP> &fxs,
2450                const std::map<TFx *, int> &zeraryFxColumnSize,
2451                const std::list<TXshColumnP> &columns, const TPointD &pos,
2452                TXsheetHandle *xshHandle, TFxHandle *fxHandle,
2453                bool addOffset = true)
2454       : m_fxs(fxs)
2455       , m_columns(columns)
2456       , m_xshHandle(xshHandle)
2457       , m_fxHandle(fxHandle) {
2458     initialize(zeraryFxColumnSize, pos, addOffset);
2459   }
2460 
isConsistent() const2461   bool isConsistent() const override {
2462     return !(m_fxs.empty() && m_columns.empty());
2463   }
2464 
2465   void redo() const override;
2466   void undo() const override;
2467 
getSize() const2468   int getSize() const override { return sizeof(*this); }
2469 
2470   QString getHistoryString() override;
2471 
2472 protected:
2473   template <typename Func>
2474   void for_each_fx(Func func) const;
2475 
2476 private:
2477   void initialize(const std::map<TFx *, int> &zeraryFxColumnSize,
2478                   const TPointD &pos, bool addOffset);
2479 };
2480 
2481 //------------------------------------------------------
2482 
initialize(const std::map<TFx *,int> & zeraryFxColumnSize,const TPointD & pos,bool addOffset)2483 void UndoPasteFxs::initialize(const std::map<TFx *, int> &zeraryFxColumnSize,
2484                               const TPointD &pos, bool addOffset) {
2485   struct locals {
2486     static void buildDagPos(TFx *fromFx, TFx *toFx, bool copyPos,
2487                             bool addOffset) {
2488       TPointD dagPos = TConst::nowhere;
2489       if (copyPos) {
2490         static const TPointD offset(30, 30);
2491 
2492         dagPos = fromFx->getAttributes()->getDagNodePos();
2493         if (addOffset &&
2494             (dagPos != TConst::nowhere))  // Shift may only happen if the copied
2495           dagPos += offset;               // position is well-formed.
2496       }
2497 
2498       toFx->getAttributes()->setDagNodePos(dagPos);
2499     }
2500 
2501     static void renamePort(TFx *fx, int p, const std::wstring &oldId,
2502                            const std::wstring &newId) {
2503       const QString &qOldId = QString::fromStdWString(oldId);
2504       const QString &qNewId = QString::fromStdWString(newId);
2505       const QString &qPortName =
2506           QString::fromStdString(fx->getInputPortName(p));
2507 
2508       if (qPortName.endsWith(qOldId)) {
2509         QString qNewPortName = qPortName;
2510         qNewPortName.replace(qOldId, qNewId);
2511         fx->renamePort(qPortName.toStdString(), qNewPortName.toStdString());
2512       }
2513     }
2514   };
2515 
2516   TXsheet *xsh    = m_xshHandle->getXsheet();
2517   bool copyDagPos = (pos != TConst::nowhere);
2518 
2519   // Initialize fxs
2520   std::list<TFxP>::iterator ft, fEnd = m_fxs.end();
2521   for (ft = m_fxs.begin(); ft != fEnd;) {
2522     TFx *fx = ft->getPointer();
2523 
2524     assert(!dynamic_cast<TZeraryColumnFx *>(fx));
2525 
2526     if (has_fx_column(fx)) {
2527       // Zerary case
2528 
2529       // Since we have no column available (WHICH IS WRONG), we'll build up a
2530       // column with
2531       // the specified column size
2532       std::map<TFx *, int>::const_iterator it = zeraryFxColumnSize.find(fx);
2533       int rows = (it == zeraryFxColumnSize.end()) ? 100 : it->second;
2534 
2535       TXshZeraryFxColumn *column = new TXshZeraryFxColumn(rows);
2536       TZeraryColumnFx *zcfx      = column->getZeraryColumnFx();
2537 
2538       zcfx->setZeraryFx(fx);
2539 
2540       int op, opCount = fx->getOutputConnectionCount();
2541       for (op = opCount - 1; op >= 0;
2542            --op)  // Output links in the zerary case were
2543       {           // assigned to the *actual* zerary
2544         TFxPort *outPort =
2545             fx->getOutputConnection(op);  // and not to a related zerary column.
2546         outPort->setFx(zcfx);  // This is an FxsData fault... BUGFIX REQUEST!
2547       }
2548 
2549       zcfx->getAttributes()->setDagNodePos(
2550           fx->getAttributes()->getDagNodePos());
2551       m_columns.push_front(column);
2552 
2553       ft = m_fxs.erase(ft);
2554       continue;
2555     }
2556 
2557     // Macro case
2558     if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(fx)) {
2559       const std::vector<TFxP> &inMacroFxs = macroFx->getFxs();
2560 
2561       size_t f, fCount = inMacroFxs.size();
2562       for (f = 0; f != fCount; ++f) {
2563         TFx *inFx                   = inMacroFxs[f].getPointer();
2564         const std::wstring &oldFxId = inFx->getFxId();
2565 
2566         // Since we're pasting a macro, we're actually pasting all of its inner
2567         // fxs,
2568         // which must have a different name (id) from the originals. However,
2569         // such ids
2570         // are actually copied in the macro's *port names* - which must
2571         // therefore be
2572         // redirected to the new names.
2573 
2574         ::initializeFx(xsh, inFx);
2575         const std::wstring &newFxId = inFx->getFxId();
2576 
2577         int ip, ipCount = macroFx->getInputPortCount();
2578         for (ip = 0; ip != ipCount; ++ip)
2579           locals::renamePort(macroFx, ip, oldFxId, newFxId);
2580         // node position of the macrofx is defined by dag-pos of inner fxs.
2581         // so we need to reset them here or pasted node will be at the same
2582         // position as the copied one.
2583         locals::buildDagPos(inFx, inFx, copyDagPos, addOffset);
2584       }
2585     }
2586 
2587     ::initializeFx(xsh, fx);
2588     locals::buildDagPos(fx, fx, copyDagPos, addOffset);
2589 
2590     ++ft;
2591   }
2592 
2593   // Filter columns
2594   auto const circularSubxsheet = [xsh](const TXshColumnP &col) -> bool {
2595     return xsh->checkCircularReferences(col.getPointer());
2596   };
2597   m_columns.erase(
2598       std::remove_if(m_columns.begin(), m_columns.end(), circularSubxsheet),
2599       m_columns.end());
2600 
2601   // Initialize columns
2602   std::list<TXshColumnP>::const_iterator ct, cEnd(m_columns.end());
2603   for (ct = m_columns.begin(); ct != cEnd; ++ct) {
2604     if (TFx *cfx = (*ct)->getFx()) {
2605       ::initializeFx(xsh, cfx);
2606       locals::buildDagPos(cfx, cfx, copyDagPos, addOffset);
2607     }
2608   }
2609 
2610   // Now, let's make a temporary container of all the stored fxs, both
2611   // normal and column-based
2612   std::vector<TFx *> fxs;
2613   fxs.reserve(m_fxs.size() + m_columns.size());
2614 
2615   for_each_fx([&fxs](TFx *fx) { fxs.push_back(fx); });
2616 
2617   // We need to store input links for these fxs
2618   size_t f, fCount = fxs.size();
2619   for (f = 0; f != fCount; ++f) {
2620     TFx *fx = fxs[f];
2621 
2622     TFx *ofx = ::getActualIn(fx);
2623     fx       = ::getActualOut(fx);
2624 
2625     int il, ilCount = ofx->getInputPortCount();
2626     for (il = 0; il != ilCount; ++il) {
2627       if (TFx *ifx = ofx->getInputPort(il)->getFx())
2628         m_links.push_back(TFxCommand::Link(ifx, ofx, il));
2629     }
2630   }
2631 
2632   // Apply the required position, if any
2633   if (pos != TConst::nowhere) {
2634     // Then, we'll take the mean difference from pos and add it to
2635     // each fx
2636     TPointD middlePos;
2637     int fxsCount = 0;
2638 
2639     std::vector<TFx *>::const_iterator ft, fEnd = fxs.end();
2640     for (ft = fxs.begin(); ft != fEnd; ++ft) {
2641       TFx *fx = *ft;
2642 
2643       const TPointD &fxPos = fx->getAttributes()->getDagNodePos();
2644       if (fxPos != TConst::nowhere) {
2645         middlePos += fxPos;
2646         ++fxsCount;
2647       }
2648     }
2649 
2650     if (fxsCount > 0) {
2651       middlePos = TPointD(middlePos.x / fxsCount, middlePos.y / fxsCount);
2652       const TPointD &offset = pos - middlePos;
2653 
2654       for (ft = fxs.begin(); ft != fEnd; ++ft) {
2655         TFx *fx = *ft;
2656 
2657         const TPointD &fxPos = fx->getAttributes()->getDagNodePos();
2658         if (fxPos != TConst::nowhere)
2659           fx->getAttributes()->setDagNodePos(fxPos + offset);
2660       }
2661     }
2662   }
2663 }
2664 
2665 //------------------------------------------------------
2666 
redo() const2667 void UndoPasteFxs::redo() const {
2668   TXsheet *xsh = m_xshHandle->getXsheet();
2669 
2670   // Iterate all normal fxs
2671   std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
2672   for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
2673     // Re-insert them in the scene
2674     addFxToCurrentScene(ft->getPointer(), xsh, false);
2675   }
2676 
2677   // Iterate columns
2678   std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
2679   for (ct = m_columns.begin(); ct != cEnd; ++ct) {
2680     // Insert them
2681     FxCommandUndo::insertColumn(xsh, ct->getPointer(),
2682                                 xsh->getFirstFreeColumnIndex(), true, false);
2683   }
2684 
2685   // Restore links
2686   size_t l, lCount = m_links.size();
2687   for (l = 0; l != lCount; ++l) FxCommandUndo::attach(xsh, m_links[l], false);
2688 
2689   m_xshHandle->notifyXsheetChanged();
2690 }
2691 
2692 //------------------------------------------------------
2693 
undo() const2694 void UndoPasteFxs::undo() const {
2695   TXsheet *xsh = m_xshHandle->getXsheet();
2696 
2697   std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
2698   for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
2699     TFx *fx = ft->getPointer();
2700 
2701     FxCommandUndo::removeFxOrColumn(xsh, fx, -1, true,
2702                                     false);  // Skip parameter links removal
2703     FxCommandUndo::makeNotCurrent(m_fxHandle, fx);
2704   }
2705 
2706   std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
2707   for (ct = m_columns.begin(); ct != cEnd; ++ct) {
2708     FxCommandUndo::removeFxOrColumn(xsh, 0, (*ct)->getIndex(), true,
2709                                     false);  // Skip parameter links removal
2710     FxCommandUndo::makeNotCurrent(m_fxHandle, (*ct)->getFx());
2711   }
2712 
2713   m_xshHandle->notifyXsheetChanged();
2714 }
2715 
2716 //------------------------------------------------------
2717 
2718 template <typename Func>
for_each_fx(Func func) const2719 void UndoPasteFxs::for_each_fx(Func func) const {
2720   std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
2721   for (ft = m_fxs.begin(); ft != fEnd; ++ft) func(ft->getPointer());
2722 
2723   std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
2724   for (ct = m_columns.begin(); ct != cEnd; ++ct)
2725     if (TFx *cfx = (*ct)->getFx()) func(cfx);
2726 }
2727 
2728 //------------------------------------------------------
2729 
getHistoryString()2730 QString UndoPasteFxs::getHistoryString() {
2731   QString str = QObject::tr("Paste Fx  :  ");
2732   std::list<TFxP>::const_iterator it;
2733   for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
2734     if (it != m_fxs.begin()) str += QString(",  ");
2735     str += QString("%1").arg(QString::fromStdWString((*it)->getName()));
2736   }
2737   return str;
2738 }
2739 
2740 //=============================================================
2741 
pasteFxs(const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,const TPointD & pos,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2742 void TFxCommand::pasteFxs(const std::list<TFxP> &fxs,
2743                           const std::map<TFx *, int> &zeraryFxColumnSize,
2744                           const std::list<TXshColumnP> &columns,
2745                           const TPointD &pos, TXsheetHandle *xshHandle,
2746                           TFxHandle *fxHandle) {
2747   std::unique_ptr<FxCommandUndo> undo(new UndoPasteFxs(
2748       fxs, zeraryFxColumnSize, columns, pos, xshHandle, fxHandle));
2749   if (undo->isConsistent()) {
2750     undo->redo();
2751     TUndoManager::manager()->add(undo.release());
2752   }
2753 }
2754 
2755 //**********************************************************************
2756 //    Add Paste Fxs  command
2757 //**********************************************************************
2758 
2759 class UndoAddPasteFxs : public UndoPasteFxs {
2760 protected:
2761   TFxCommand::Link m_linkIn;  //!< Input link to be re-established on redo
2762 
2763 public:
UndoAddPasteFxs(TFx * inFx,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2764   UndoAddPasteFxs(TFx *inFx, const std::list<TFxP> &fxs,
2765                   const std::map<TFx *, int> &zeraryFxColumnSize,
2766                   const std::list<TXshColumnP> &columns,
2767                   TXsheetHandle *xshHandle, TFxHandle *fxHandle)
2768       : UndoPasteFxs(fxs, zeraryFxColumnSize, columns, TConst::nowhere,
2769                      xshHandle, fxHandle) {
2770     initialize(inFx);
2771   }
2772 
2773   void redo() const override;
2774 
getSize() const2775   int getSize() const override { return sizeof(*this); }
2776 
2777 private:
2778   void initialize(TFx *inFx);
2779 };
2780 
2781 //------------------------------------------------------
2782 
initialize(TFx * inFx)2783 void UndoAddPasteFxs::initialize(TFx *inFx) {
2784   if (!(inFx && UndoPasteFxs::isConsistent())) return;
2785 
2786   // NOTE: Any zerary (shouldn't be there anyway) has already been
2787   //       moved to columns at this point
2788 
2789   TXsheet *xsh = m_xshHandle->getXsheet();
2790 
2791   // Ensure that inFx and outFx are not in a macro Fx
2792   if (::isInsideAMacroFx(inFx, xsh)) {
2793     m_fxs.clear(), m_columns.clear();
2794     return;
2795   }
2796 
2797   // Get the first fx to be inserted, and follow links down
2798   // (until an empty input port at index 0 is found)
2799   TFx *ifx = FxCommandUndo::leftmostConnectedFx(m_fxs.front().getPointer());
2800 
2801   // Then, we have the link to be established
2802   m_linkIn = TFxCommand::Link(inFx, ifx, 0);
2803 
2804   // Furthermore, clone the group stack from inFx into each inserted fx
2805   auto const clone_fun =
2806       static_cast<void (*)(TFx *, TFx *)>(FxCommandUndo::cloneGroupStack);
2807   for_each_fx([inFx, clone_fun](TFx *toFx) { clone_fun(inFx, toFx); });
2808 }
2809 
2810 //------------------------------------------------------
2811 
redo() const2812 void UndoAddPasteFxs::redo() const {
2813   if (m_linkIn.m_inputFx) {
2814     TXsheet *xsh = m_xshHandle->getXsheet();
2815 
2816     // Further apply the stored link
2817     FxCommandUndo::attach(xsh, m_linkIn, false);
2818 
2819     // Copiare l'indice di gruppo dell'fx di input
2820     auto const copy_fun =
2821         static_cast<void (*)(TFx *, TFx *)>(FxCommandUndo::copyGroupEditLevel);
2822     for_each_fx([this, copy_fun](TFx *toFx) {
2823       copy_fun(m_linkIn.m_inputFx.getPointer(), toFx);
2824     });
2825   }
2826 
2827   UndoPasteFxs::redo();
2828 }
2829 
2830 //=============================================================
2831 
addPasteFxs(TFx * inFx,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2832 void TFxCommand::addPasteFxs(TFx *inFx, const std::list<TFxP> &fxs,
2833                              const std::map<TFx *, int> &zeraryFxColumnSize,
2834                              const std::list<TXshColumnP> &columns,
2835                              TXsheetHandle *xshHandle, TFxHandle *fxHandle) {
2836   std::unique_ptr<FxCommandUndo> undo(new UndoAddPasteFxs(
2837       inFx, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
2838   if (undo->isConsistent()) {
2839     // NOTE: (inFx == 0) is considered consistent, as long as
2840     // UndoPasteFxs::isConsistent()
2841     undo->redo();
2842     TUndoManager::manager()->add(undo.release());
2843   }
2844 }
2845 
2846 //**********************************************************************
2847 //    Insert Paste Fxs  command
2848 //**********************************************************************
2849 
2850 class UndoInsertPasteFxs final : public UndoAddPasteFxs {
2851   TFxCommand::Link m_linkOut;  //!< Output link to be re-established
2852                                //!< on redo
2853 public:
UndoInsertPasteFxs(const TFxCommand::Link & link,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2854   UndoInsertPasteFxs(const TFxCommand::Link &link, const std::list<TFxP> &fxs,
2855                      const std::map<TFx *, int> &zeraryFxColumnSize,
2856                      const std::list<TXshColumnP> &columns,
2857                      TXsheetHandle *xshHandle, TFxHandle *fxHandle)
2858       : UndoAddPasteFxs(link.m_inputFx.getPointer(), fxs, zeraryFxColumnSize,
2859                         columns, xshHandle, fxHandle) {
2860     initialize(link);
2861   }
2862 
2863   void redo() const override;
2864   void undo() const override;
2865 
getSize() const2866   int getSize() const override { return sizeof(*this); }
2867 
2868 private:
2869   void initialize(const TFxCommand::Link &link);
2870 };
2871 
2872 //------------------------------------------------------
2873 
initialize(const TFxCommand::Link & link)2874 void UndoInsertPasteFxs::initialize(const TFxCommand::Link &link) {
2875   if (!UndoPasteFxs::isConsistent()) return;
2876 
2877   TXsheet *xsh = m_xshHandle->getXsheet();
2878   TFx *inFx    = link.m_inputFx.getPointer();
2879   TFx *outFx   = link.m_outputFx.getPointer();
2880 
2881   // Ensure consistency
2882   if (!(inFx && outFx) || ::isInsideAMacroFx(outFx, xsh)) {
2883     m_fxs.clear(), m_columns.clear();
2884     return;
2885   }
2886 
2887   // Get the first fx to be inserted, and follow links up
2888   // (to a no output fx)
2889   TFx *ofx = FxCommandUndo::rightmostConnectedFx(m_fxs.front().getPointer());
2890 
2891   // Now, store the appropriate output link
2892   m_linkOut = TFxCommand::Link(ofx, outFx, link.m_index);
2893 }
2894 
2895 //------------------------------------------------------
2896 
redo() const2897 void UndoInsertPasteFxs::redo() const {
2898   TXsheet *xsh = m_xshHandle->getXsheet();
2899 
2900   // Further apply the stored link pair
2901   FxCommandUndo::attach(xsh, m_linkOut, false);
2902 
2903   if (m_linkOut.m_index < 0)
2904     xsh->getFxDag()->removeFromXsheet(m_linkIn.m_inputFx.getPointer());
2905 
2906   UndoAddPasteFxs::redo();
2907 }
2908 
2909 //------------------------------------------------------
2910 
undo() const2911 void UndoInsertPasteFxs::undo() const {
2912   TXsheet *xsh = m_xshHandle->getXsheet();
2913 
2914   // Reattach the original configuration
2915   TFxCommand::Link orig(m_linkIn.m_inputFx, m_linkOut.m_outputFx,
2916                         m_linkOut.m_index);
2917   FxCommandUndo::attach(xsh, orig, false);
2918 
2919   UndoAddPasteFxs::undo();
2920 }
2921 
2922 //=============================================================
2923 
insertPasteFxs(const Link & link,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2924 void TFxCommand::insertPasteFxs(const Link &link, const std::list<TFxP> &fxs,
2925                                 const std::map<TFx *, int> &zeraryFxColumnSize,
2926                                 const std::list<TXshColumnP> &columns,
2927                                 TXsheetHandle *xshHandle, TFxHandle *fxHandle) {
2928   std::unique_ptr<FxCommandUndo> undo(new UndoInsertPasteFxs(
2929       link, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
2930   if (undo->isConsistent()) {
2931     undo->redo();
2932     TUndoManager::manager()->add(undo.release());
2933   }
2934 }
2935 
2936 //**********************************************************************
2937 //    Replace Paste Fxs  command
2938 //**********************************************************************
2939 
2940 class UndoReplacePasteFxs final : public UndoAddPasteFxs {
2941   std::unique_ptr<DeleteFxOrColumnUndo> m_deleteFxUndo;
2942 
2943   TFx *m_fx, *m_rightmostFx;
2944 
2945 public:
UndoReplacePasteFxs(TFx * fx,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)2946   UndoReplacePasteFxs(TFx *fx, const std::list<TFxP> &fxs,
2947                       const std::map<TFx *, int> &zeraryFxColumnSize,
2948                       const std::list<TXshColumnP> &columns,
2949                       TXsheetHandle *xshHandle, TFxHandle *fxHandle)
2950       : UndoAddPasteFxs(inFx(fx), fxs, zeraryFxColumnSize, columns, xshHandle,
2951                         fxHandle)
2952       , m_deleteFxUndo(new DeleteFxOrColumnUndo(fx, xshHandle, fxHandle))
2953       , m_fx(fx)
2954       , m_rightmostFx() {
2955     initialize();
2956   }
2957 
isConsistent() const2958   bool isConsistent() const override {
2959     return UndoAddPasteFxs::isConsistent() && m_deleteFxUndo->isConsistent();
2960   }
2961   void redo() const override;
2962   void undo() const override;
2963 
getSize() const2964   int getSize() const override { return sizeof(*this); }
2965 
2966 private:
inFx(const TFx * fx)2967   static TFx *inFx(const TFx *fx) {
2968     return (fx && fx->getInputPortCount() > 0) ? fx->getInputPort(0)->getFx()
2969                                                : 0;
2970   }
2971 
2972   void initialize();
2973 };
2974 
2975 //------------------------------------------------------
2976 
initialize()2977 void UndoReplacePasteFxs::initialize() {
2978   if (m_fxs.empty()) return;
2979 
2980   TXsheet *xsh = m_xshHandle->getXsheet();
2981   FxDag *fxDag = xsh->getFxDag();
2982 
2983   // Get the first fx to be inserted, and follow links up
2984   // (to a no output fx)
2985   m_rightmostFx =
2986       FxCommandUndo::rightmostConnectedFx(this->m_fxs.front().getPointer());
2987 
2988   // Then, add to the list of links to be inserted upon redo
2989   int ol, olCount = m_fx->getOutputConnectionCount();
2990   for (ol = 0; ol != olCount; ++ol) {
2991     TFxPort *port = m_fx->getOutputConnection(ol);
2992     TFx *ownerFx  = port->getOwnerFx();
2993 
2994     TCG_ASSERT(port && ownerFx, continue);
2995 
2996     int p = ::inputPortIndex(ownerFx, port);
2997     TCG_ASSERT(p < ownerFx->getInputPortCount(), continue);
2998 
2999     this->m_links.push_back(TFxCommand::Link(m_rightmostFx, ownerFx, p));
3000   }
3001 
3002   if (fxDag->getTerminalFxs()->containsFx(m_fx))
3003     this->m_links.push_back(
3004         TFxCommand::Link(m_rightmostFx, fxDag->getXsheetFx(), -1));
3005 }
3006 
3007 //------------------------------------------------------
3008 
redo() const3009 void UndoReplacePasteFxs::redo() const {
3010   TXsheet *xsh = m_xshHandle->getXsheet();
3011   FxDag *fxDag = xsh->getFxDag();
3012 
3013   // Deleting m_fx would attach its input to the xsheet, if m_fx is terminal.
3014   // In our case, however, it needs to be attached to the replacement fx - so,
3015   // let's detach m_fx from the xsheet
3016   fxDag->removeFromXsheet(m_fx);
3017 
3018   // Then, delete the fx and insert the replacement. Note that this order is
3019   // required to ensure the correct dag positions
3020   m_deleteFxUndo->redo();
3021   UndoAddPasteFxs::redo();
3022 }
3023 
3024 //------------------------------------------------------
3025 
undo() const3026 void UndoReplacePasteFxs::undo() const {
3027   TXsheet *xsh = m_xshHandle->getXsheet();
3028   FxDag *fxDag = xsh->getFxDag();
3029 
3030   // Remove m_lastFx's output connections - UndoAddPasteFxs would try to
3031   // redirect them to the replaced fx's input (due to the 'blind' detach
3032   // command)
3033   if (m_rightmostFx) {
3034     int ol, olCount = m_rightmostFx->getOutputConnectionCount();
3035     for (ol = olCount - 1; ol >= 0; --ol)
3036       if (TFxPort *port = m_rightmostFx->getOutputConnection(ol))
3037         port->setFx(0);
3038 
3039     fxDag->removeFromXsheet(m_rightmostFx);
3040   }
3041 
3042   // Reverse the applied commands. Again, the order prevents 'bumped' dag
3043   // positions
3044 
3045   UndoAddPasteFxs::undo();  // This would bridge the inserted fxs' inputs with
3046                             // their outputs
3047   m_deleteFxUndo->undo();  // This also re-establishes the original output links
3048 }
3049 
3050 //=============================================================
3051 
replacePasteFxs(TFx * inFx,const std::list<TFxP> & fxs,const std::map<TFx *,int> & zeraryFxColumnSize,const std::list<TXshColumnP> & columns,TXsheetHandle * xshHandle,TFxHandle * fxHandle)3052 void TFxCommand::replacePasteFxs(TFx *inFx, const std::list<TFxP> &fxs,
3053                                  const std::map<TFx *, int> &zeraryFxColumnSize,
3054                                  const std::list<TXshColumnP> &columns,
3055                                  TXsheetHandle *xshHandle,
3056                                  TFxHandle *fxHandle) {
3057   std::unique_ptr<FxCommandUndo> undo(new UndoReplacePasteFxs(
3058       inFx, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
3059   if (undo->isConsistent()) {
3060     undo->redo();
3061     TUndoManager::manager()->add(undo.release());
3062   }
3063 }
3064 
3065 //**********************************************************************
3066 //    Disconnect Fxs  command
3067 //**********************************************************************
3068 
3069 class UndoDisconnectFxs : public FxCommandUndo {
3070 protected:
3071   std::list<TFxP> m_fxs;
3072   TFx *m_leftFx, *m_rightFx;
3073 
3074   // NOTE: Although we'll detach only 1 input link, fxs with dynamic ports still
3075   // require us
3076   // to store the whole ports configuration
3077   std::vector<TFxCommand::Link> m_undoLinksIn, m_undoLinksOut,
3078       m_undoTerminalLinks;
3079   std::vector<QPair<TFxP, TPointD>> m_undoDagPos, m_redoDagPos;
3080 
3081   TXsheetHandle *m_xshHandle;
3082 
3083 public:
UndoDisconnectFxs(const std::list<TFxP> & fxs,const QList<QPair<TFxP,TPointD>> & oldFxPos,TXsheetHandle * xshHandle)3084   UndoDisconnectFxs(const std::list<TFxP> &fxs,
3085                     const QList<QPair<TFxP, TPointD>> &oldFxPos,
3086                     TXsheetHandle *xshHandle)
3087       : m_fxs(fxs)
3088       , m_xshHandle(xshHandle)
3089       , m_undoDagPos(oldFxPos.begin(), oldFxPos.end()) {
3090     initialize();
3091   }
3092 
isConsistent() const3093   bool isConsistent() const override { return !m_fxs.empty(); }
3094 
3095   void redo() const override;
3096   void undo() const override;
3097 
getSize() const3098   int getSize() const override { return sizeof(*this); }
3099 
getHistoryString()3100   QString getHistoryString() override { return QObject::tr("Disconnect Fx"); }
3101 
3102 private:
3103   void initialize();
3104 
applyPos(const QPair<TFxP,TPointD> & pair)3105   static void applyPos(const QPair<TFxP, TPointD> &pair) {
3106     pair.first->getAttributes()->setDagNodePos(pair.second);
3107   }
3108 
attach(TXsheet * xsh,const TFxCommand::Link & link)3109   static void attach(TXsheet *xsh, const TFxCommand::Link &link) {
3110     FxCommandUndo::attach(xsh, link, false);
3111   }
detachXsh(TXsheet * xsh,const TFxCommand::Link & link)3112   static void detachXsh(TXsheet *xsh, const TFxCommand::Link &link) {
3113     xsh->getFxDag()->removeFromXsheet(link.m_inputFx.getPointer());
3114   }
3115 };
3116 
3117 //======================================================
3118 
initialize()3119 void UndoDisconnectFxs::initialize() {
3120   TXsheet *xsh = m_xshHandle->getXsheet();
3121   FxDag *fxDag = xsh->getFxDag();
3122 
3123   // Don't deal with fxs inside a macro fx
3124   ::FilterInsideAMacro insideAMacro_fun = {xsh};
3125   if (std::find_if(m_fxs.begin(), m_fxs.end(), insideAMacro_fun) != m_fxs.end())
3126     m_fxs.clear();
3127 
3128   if (m_fxs.empty()) return;
3129 
3130   // Build fxs data
3131   auto const contains = [this](TFx const *fx) -> bool {
3132     return std::count_if(this->m_fxs.begin(), this->m_fxs.end(),
3133                          [fx](TFxP &f) { return f.getPointer() == fx; }) > 0;
3134   };
3135 
3136   m_leftFx =
3137       FxCommandUndo::leftmostConnectedFx(m_fxs.front().getPointer(), contains);
3138   m_rightFx =
3139       FxCommandUndo::rightmostConnectedFx(m_fxs.front().getPointer(), contains);
3140 
3141   // Store sensible original data for the undo
3142   m_undoLinksIn  = FxCommandUndo::inputLinks(xsh, m_leftFx);
3143   m_undoLinksOut = FxCommandUndo::outputLinks(xsh, m_rightFx);
3144 
3145   std::vector<TFxCommand::Link>::const_iterator lt, lEnd = m_undoLinksIn.end();
3146   for (lt = m_undoLinksIn.begin(); lt != lEnd; ++lt)
3147     if (fxDag->getTerminalFxs()->containsFx(lt->m_inputFx.getPointer()))
3148       m_undoTerminalLinks.push_back(TFxCommand::Link(lt->m_inputFx.getPointer(),
3149                                                      fxDag->getXsheetFx(), -1));
3150 
3151   std::vector<QPair<TFxP, TPointD>> v;
3152   for (auto const &e : m_undoDagPos) {
3153     v.emplace_back(e.first, e.first->getAttributes()->getDagNodePos());
3154   }
3155   m_redoDagPos = std::move(v);
3156   m_redoDagPos.shrink_to_fit();
3157 }
3158 
3159 //------------------------------------------------------
3160 
redo() const3161 void UndoDisconnectFxs::redo() const {
3162   TXsheet *xsh = m_xshHandle->getXsheet();
3163 
3164   // Detach the first port only - I'm not sure it should really be like this,
3165   // but it's
3166   // legacy, and altering it would require to revise the 'simulation' procedures
3167   // in fxschematicscene.cpp...  (any command simulation should be done here,
3168   // btw)
3169   FxCommandUndo::detachFxs(xsh, m_leftFx, m_rightFx, false);
3170   if (m_leftFx->getInputPortCount() > 0) m_leftFx->getInputPort(0)->setFx(0);
3171 
3172   // This is also convenient, since fxs with dynamic groups will maintain all
3173   // BUT 1
3174   // port - thus preventing us from dealing with that, since 1 port is always
3175   // available
3176   // on fxs with dynamic ports...
3177 
3178   std::for_each(m_redoDagPos.begin(), m_redoDagPos.end(), applyPos);
3179 
3180   m_xshHandle->notifyXsheetChanged();
3181 }
3182 
3183 //------------------------------------------------------
3184 
undo() const3185 void UndoDisconnectFxs::undo() const {
3186   typedef void (*LinkFun)(TXsheet * xsh, const TFxCommand::Link &);
3187 
3188   TXsheet *xsh = m_xshHandle->getXsheet();
3189   FxDag *fxDag = xsh->getFxDag();
3190 
3191   // Restore the old links
3192   auto const attacher = [xsh](const TFxCommand::Link &link) {
3193     return UndoDisconnectFxs::attach(xsh, link);
3194   };
3195   auto const xshDetacher = [xsh](const TFxCommand::Link &link) {
3196     return UndoDisconnectFxs::detachXsh(xsh, link);
3197   };
3198 
3199   std::for_each(m_undoLinksIn.begin(), m_undoLinksIn.end(), attacher);
3200   std::for_each(m_undoLinksOut.begin(), m_undoLinksOut.end(), attacher);
3201 
3202   std::for_each(m_undoLinksIn.begin(), m_undoLinksIn.end(), xshDetacher);
3203   std::for_each(m_undoTerminalLinks.begin(), m_undoTerminalLinks.end(),
3204                 attacher);
3205 
3206   // Restore old positions
3207   std::for_each(m_undoDagPos.begin(), m_undoDagPos.end(), applyPos);
3208 
3209   m_xshHandle->notifyXsheetChanged();
3210 }
3211 
3212 //======================================================
3213 
disconnectFxs(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle,const QList<QPair<TFxP,TPointD>> & fxPos)3214 void TFxCommand::disconnectFxs(const std::list<TFxP> &fxs,
3215                                TXsheetHandle *xshHandle,
3216                                const QList<QPair<TFxP, TPointD>> &fxPos) {
3217   std::unique_ptr<FxCommandUndo> undo(
3218       new UndoDisconnectFxs(fxs, fxPos, xshHandle));
3219   if (undo->isConsistent()) {
3220     undo->redo();
3221     TUndoManager::manager()->add(undo.release());
3222   }
3223 }
3224 
3225 //**********************************************************************
3226 //    Connect Fxs  command
3227 //**********************************************************************
3228 
3229 class UndoConnectFxs final : public UndoDisconnectFxs {
3230   struct GroupData;
3231 
3232 private:
3233   TFxCommand::Link m_link;
3234   std::vector<GroupData> m_undoGroupDatas;
3235 
3236 public:
UndoConnectFxs(const TFxCommand::Link & link,const std::list<TFxP> & fxs,const QList<QPair<TFxP,TPointD>> & fxPos,TXsheetHandle * xshHandle)3237   UndoConnectFxs(const TFxCommand::Link &link, const std::list<TFxP> &fxs,
3238                  const QList<QPair<TFxP, TPointD>> &fxPos,
3239                  TXsheetHandle *xshHandle)
3240       : UndoDisconnectFxs(fxs, fxPos, xshHandle), m_link(link) {
3241     initialize();
3242   }
3243 
isConsistent() const3244   bool isConsistent() const override { return !m_fxs.empty(); }
3245 
3246   void redo() const override;
3247   void undo() const override;
3248 
getSize() const3249   int getSize() const override { return sizeof(*this); }
3250 
3251   QString getHistoryString() override;
3252 
3253 private:
3254   void initialize();
3255 
applyPos(const QPair<TFxP,TPointD> & pair)3256   static void applyPos(const QPair<TFxP, TPointD> &pair) {
3257     pair.first->getAttributes()->setDagNodePos(pair.second);
3258   }
3259 };
3260 
3261 //======================================================
3262 
3263 struct UndoConnectFxs::GroupData {
3264   TFx *m_fx;
3265   QStack<int> m_groupIds;
3266   QStack<std::wstring> m_groupNames;
3267   int m_editingGroup;
3268 
3269 public:
3270   GroupData(TFx *fx);
3271   void restore() const;
3272 };
3273 
3274 //------------------------------------------------------
3275 
GroupData(TFx * fx)3276 UndoConnectFxs::GroupData::GroupData(TFx *fx)
3277     : m_fx(fx)
3278     , m_groupIds(fx->getAttributes()->getGroupIdStack())
3279     , m_groupNames(fx->getAttributes()->getGroupNameStack())
3280     , m_editingGroup(fx->getAttributes()->getEditingGroupId()) {}
3281 
3282 //------------------------------------------------------
3283 
restore() const3284 void UndoConnectFxs::GroupData::restore() const {
3285   assert(!m_groupIds.empty());
3286 
3287   FxCommandUndo::cloneGroupStack(m_groupIds, m_groupNames, m_fx);
3288   FxCommandUndo::copyGroupEditLevel(m_editingGroup, m_fx);
3289 }
3290 
3291 //======================================================
3292 
initialize()3293 void UndoConnectFxs::initialize() {
3294   if (!UndoDisconnectFxs::isConsistent()) return;
3295 
3296   TCG_ASSERT(m_link.m_inputFx && m_link.m_outputFx, m_fxs.clear(); return );
3297 
3298   // Store sensible original data for the undo
3299   m_undoGroupDatas.reserve(m_fxs.size());
3300 
3301   std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
3302   for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
3303     if ((*ft)->getAttributes()->isGrouped())
3304       m_undoGroupDatas.push_back(GroupData((*ft).getPointer()));
3305   }
3306 }
3307 
3308 //------------------------------------------------------
3309 
redo() const3310 void UndoConnectFxs::redo() const {
3311   UndoDisconnectFxs::redo();
3312 
3313   TXsheet *xsh = m_xshHandle->getXsheet();
3314 
3315   // Apply the new links
3316   FxCommandUndo::insertFxs(xsh, m_link, m_leftFx, m_rightFx);
3317 
3318   // Copy the input fx's group data
3319   TFx *inFx = m_link.m_inputFx.getPointer();
3320 
3321   std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
3322   for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
3323     TFx *fx = (*ft).getPointer();
3324 
3325     FxCommandUndo::cloneGroupStack(inFx, fx);
3326     FxCommandUndo::copyGroupEditLevel(inFx, fx);
3327   }
3328 
3329   m_xshHandle->notifyXsheetChanged();
3330 }
3331 
3332 //------------------------------------------------------
3333 
undo() const3334 void UndoConnectFxs::undo() const {
3335   TXsheet *xsh = m_xshHandle->getXsheet();
3336 
3337   // Undo the insert
3338   FxCommandUndo::detachFxs(xsh, m_leftFx, m_rightFx);
3339   FxCommandUndo::attach(xsh, m_link, false);
3340 
3341   // Restore the old fxs' group data
3342   for (auto const &groupData : m_undoGroupDatas) {
3343     groupData.restore();
3344   }
3345 
3346   UndoDisconnectFxs::undo();
3347 }
3348 
3349 //------------------------------------------------------
3350 
getHistoryString()3351 QString UndoConnectFxs::getHistoryString() {
3352   return QObject::tr("Connect Fx : %1 - %2")
3353       .arg(QString::fromStdWString(m_leftFx->getName()))
3354       .arg(QString::fromStdWString(m_rightFx->getName()));
3355 }
3356 
3357 //======================================================
3358 
connectFxs(const Link & link,const std::list<TFxP> & fxs,TXsheetHandle * xshHandle,const QList<QPair<TFxP,TPointD>> & fxPos)3359 void TFxCommand::connectFxs(const Link &link, const std::list<TFxP> &fxs,
3360                             TXsheetHandle *xshHandle,
3361                             const QList<QPair<TFxP, TPointD>> &fxPos) {
3362   std::unique_ptr<FxCommandUndo> undo(
3363       new UndoConnectFxs(link, fxs, fxPos, xshHandle));
3364   if (undo->isConsistent()) {
3365     undo->redo();
3366     TUndoManager::manager()->add(undo.release());
3367   }
3368 }
3369 
3370 //**********************************************************************
3371 //    Set Parent  command
3372 //**********************************************************************
3373 
3374 class SetParentUndo final : public FxCommandUndo {
3375   TFxP m_oldFx, m_newFx, m_parentFx;
3376   int m_parentPort;
3377 
3378   bool m_removeFromXsheet;
3379 
3380   TXsheetHandle *m_xshHandle;
3381 
3382 public:
SetParentUndo(TFx * fx,TFx * parentFx,int parentFxPort,TXsheetHandle * xshHandle)3383   SetParentUndo(TFx *fx, TFx *parentFx, int parentFxPort,
3384                 TXsheetHandle *xshHandle)
3385       : m_newFx(fx)
3386       , m_parentFx(parentFx)
3387       , m_parentPort(parentFxPort)
3388       , m_xshHandle(xshHandle) {
3389     initialize();
3390   }
3391 
isConsistent() const3392   bool isConsistent() const override { return m_parentFx; }
3393 
3394   void redo() const override;
3395   void redo_() const;
3396   void undo() const override;
3397 
getSize() const3398   int getSize() const override { return sizeof(*this); }
3399 
3400 private:
3401   void initialize();
3402 };
3403 
3404 //------------------------------------------------------
3405 
initialize()3406 void SetParentUndo::initialize() {
3407   if (!m_parentFx) return;
3408 
3409   // NOTE: We cannot store this directly, since it's the actual out that owns
3410   // the actual in, not viceversa
3411   TFx *parentFx = ::getActualIn(m_parentFx.getPointer());
3412 
3413   TXsheet *xsh = m_xshHandle->getXsheet();
3414   FxDag *fxDag = xsh->getFxDag();
3415 
3416   assert(m_parentPort < parentFx->getInputPortCount());
3417   assert(m_parentPort >= 0);
3418 
3419   m_oldFx = parentFx->getInputPort(m_parentPort)->getFx();
3420 
3421   m_removeFromXsheet =  // This is a bit odd. The legacy behavior of the
3422       (m_newFx &&       // setParent() (ie connect 2 fxs with a link, I know
3423        (m_newFx->getOutputConnectionCount() ==
3424         0) &&  // the name is bad) command is that of *removing terminal
3425        fxDag->getTerminalFxs()->containsFx(
3426            m_newFx.getPointer()) &&  // links* on the input fx (m_newFx in our
3427                                      // case).
3428        m_parentFx != fxDag->getXsheetFx());  // I've retained this behavior
3429                                              // since it can be handy
3430   // for users, but only if the xsheet link is the SOLE output
3431   if (::isInsideAMacroFx(m_parentFx.getPointer(), xsh) ||  // link.
3432       ::isInsideAMacroFx(m_oldFx.getPointer(), xsh) ||
3433       ::isInsideAMacroFx(m_newFx.getPointer(), xsh))
3434     m_parentFx = TFxP();
3435 }
3436 
3437 //------------------------------------------------------
3438 
redo() const3439 void SetParentUndo::redo() const {
3440   /*
3441 Due to compatibility issues from *schematicnode.cpp files, the "do" operation
3442 cannot
3443 signal changes to the scene/xsheet... but the *re*do must.
3444 */
3445 
3446   redo_();
3447   m_xshHandle->notifyXsheetChanged();
3448 }
3449 
3450 //------------------------------------------------------
3451 
redo_() const3452 void SetParentUndo::redo_() const {
3453   TXsheet *xsh = m_xshHandle->getXsheet();
3454 
3455   TFx *parentFx = ::getActualIn(m_parentFx.getPointer());
3456   FxCommandUndo::attach(xsh, m_newFx.getPointer(), parentFx, m_parentPort,
3457                         false);
3458 
3459   if (m_removeFromXsheet)
3460     xsh->getFxDag()->removeFromXsheet(m_newFx.getPointer());
3461 }
3462 
3463 //------------------------------------------------------
3464 
undo() const3465 void SetParentUndo::undo() const {
3466   TXsheet *xsh = m_xshHandle->getXsheet();
3467 
3468   TFx *parentFx = ::getActualIn(m_parentFx.getPointer());
3469   FxCommandUndo::attach(xsh, m_oldFx.getPointer(), parentFx, m_parentPort,
3470                         false);
3471 
3472   if (m_removeFromXsheet) xsh->getFxDag()->addToXsheet(m_newFx.getPointer());
3473 
3474   m_xshHandle->notifyXsheetChanged();
3475 }
3476 
3477 //======================================================
3478 
setParent(TFx * fx,TFx * parentFx,int parentFxPort,TXsheetHandle * xshHandle)3479 void TFxCommand::setParent(TFx *fx, TFx *parentFx, int parentFxPort,
3480                            TXsheetHandle *xshHandle) {
3481   if (dynamic_cast<TXsheetFx *>(parentFx) || parentFxPort < 0) {
3482     std::unique_ptr<ConnectNodesToXsheetUndo> undo(
3483         new ConnectNodesToXsheetUndo(std::list<TFxP>(1, fx), xshHandle));
3484     if (undo->isConsistent()) {
3485       undo->redo_();
3486       TUndoManager::manager()->add(undo.release());
3487     }
3488   } else {
3489     std::unique_ptr<SetParentUndo> undo(
3490         new SetParentUndo(fx, parentFx, parentFxPort, xshHandle));
3491     if (undo->isConsistent()) {
3492       undo->redo_();
3493       TUndoManager::manager()->add(undo.release());
3494     }
3495   }
3496 }
3497 
3498 //**********************************************************************
3499 //    Rename Fx  command
3500 //**********************************************************************
3501 
3502 class UndoRenameFx final : public FxCommandUndo {
3503   TFxP m_fx;
3504   std::wstring m_newName, m_oldName;
3505 
3506   TXsheetHandle *m_xshHandle;
3507 
3508 public:
UndoRenameFx(TFx * fx,const std::wstring & newName,TXsheetHandle * xshHandle)3509   UndoRenameFx(TFx *fx, const std::wstring &newName, TXsheetHandle *xshHandle)
3510       : m_fx(fx)
3511       , m_newName(newName)
3512       , m_oldName(::getActualIn(fx)->getName())
3513       , m_xshHandle(xshHandle) {
3514     assert(fx);
3515   }
3516 
isConsistent() const3517   bool isConsistent() const override { return true; }
3518 
redo() const3519   void redo() const override {
3520     redo_();
3521     m_xshHandle->notifyXsheetChanged();
3522   }
3523 
redo_() const3524   void redo_() const { ::getActualIn(m_fx.getPointer())->setName(m_newName); }
3525 
undo() const3526   void undo() const override {
3527     ::getActualIn(m_fx.getPointer())->setName(m_oldName);
3528     m_xshHandle->notifyXsheetChanged();
3529   }
3530 
getSize() const3531   int getSize() const override { return sizeof(*this); }
3532 
getHistoryString()3533   QString getHistoryString() override {
3534     return QObject::tr("Rename Fx : %1 > %2")
3535         .arg(QString::fromStdWString(m_oldName))
3536         .arg(QString::fromStdWString(m_newName));
3537   }
3538 };
3539 
3540 //======================================================
3541 
renameFx(TFx * fx,const std::wstring & newName,TXsheetHandle * xshHandle)3542 void TFxCommand::renameFx(TFx *fx, const std::wstring &newName,
3543                           TXsheetHandle *xshHandle) {
3544   if (!fx) return;
3545 
3546   std::unique_ptr<UndoRenameFx> undo(new UndoRenameFx(fx, newName, xshHandle));
3547   if (undo->isConsistent()) {
3548     undo->redo_();
3549     TUndoManager::manager()->add(undo.release());
3550   }
3551 }
3552 
3553 //**********************************************************************
3554 //    Group Fxs  command
3555 //**********************************************************************
3556 
3557 class UndoGroupFxs : public FxCommandUndo {
3558 public:
3559   struct GroupData {
3560     TFxP m_fx;
3561     mutable int m_groupIndex;  //! AKA group \a position (not \a id).
3562 
GroupDataUndoGroupFxs::GroupData3563     GroupData(const TFxP &fx, int groupIdx = -1)
3564         : m_fx(fx), m_groupIndex(groupIdx) {}
3565   };
3566 
3567 protected:
3568   std::vector<GroupData> m_groupData;
3569   int m_groupId;
3570 
3571   TXsheetHandle *m_xshHandle;
3572 
3573 public:
UndoGroupFxs(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)3574   UndoGroupFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
3575       : m_groupData(fxs.begin(), fxs.end()), m_xshHandle(xshHandle) {
3576     initialize();
3577   }
3578 
isConsistent() const3579   bool isConsistent() const override { return !m_groupData.empty(); }
3580 
3581   void redo() const override;
3582   void undo() const override;
3583 
getSize() const3584   int getSize() const override { return sizeof(*this); }
3585 
getHistoryString()3586   QString getHistoryString() override { return QObject::tr("Group Fx"); }
3587 
3588 protected:
UndoGroupFxs(int groupId,TXsheetHandle * xshHandle)3589   UndoGroupFxs(int groupId, TXsheetHandle *xshHandle)
3590       : m_groupId(groupId), m_xshHandle(xshHandle) {}
3591 
3592 private:
3593   void initialize();
3594 };
3595 
3596 //------------------------------------------------------
3597 
initialize()3598 void UndoGroupFxs::initialize() {
3599   struct locals {
3600     inline static bool isXsheetFx(const GroupData &gd) {
3601       return dynamic_cast<TXsheet *>(gd.m_fx.getPointer());
3602     }
3603   };
3604 
3605   TXsheet *xsh = m_xshHandle->getXsheet();
3606   FxDag *fxDag = xsh->getFxDag();
3607 
3608   // Build a group id for the new group
3609   m_groupId = fxDag->getNewGroupId();
3610 
3611   // The xsheet fx must never be grouped
3612   m_groupData.erase(std::remove_if(m_groupData.begin(), m_groupData.end(),
3613                                    &locals::isXsheetFx),
3614                     m_groupData.end());
3615 
3616   // Scan for macro fxs. A macro's internal fxs must be added to the group data,
3617   // too
3618   // Yep, this is one of the few fx commands that do not require macro
3619   // explosion.
3620   size_t g, gCount = m_groupData.size();
3621   for (g = 0; g != gCount; ++g) {
3622     if (TMacroFx *macro =
3623             dynamic_cast<TMacroFx *>(m_groupData[g].m_fx.getPointer())) {
3624       const std::vector<TFxP> &internalFxs = macro->getFxs();
3625 
3626       std::vector<TFxP>::const_iterator ft, fEnd = internalFxs.end();
3627       for (ft = internalFxs.begin(); ft != fEnd; ++ft)
3628         m_groupData.push_back(*ft);
3629     }
3630   }
3631 }
3632 
3633 //------------------------------------------------------
3634 
redo() const3635 void UndoGroupFxs::redo() const {
3636   const std::wstring groupName = L"Group " + std::to_wstring(m_groupId);
3637 
3638   std::vector<GroupData>::const_iterator gt, gEnd = m_groupData.end();
3639   for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
3640     // Insert the group id in the fx
3641     gt->m_groupIndex = gt->m_fx->getAttributes()->setGroupId(m_groupId);
3642     gt->m_fx->getAttributes()->setGroupName(groupName);
3643   }
3644 
3645   m_xshHandle->notifyXsheetChanged();
3646 }
3647 
3648 //------------------------------------------------------
3649 
undo() const3650 void UndoGroupFxs::undo() const {
3651   std::vector<GroupData>::const_iterator gt, gEnd = m_groupData.end();
3652   for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
3653     TCG_ASSERT(gt->m_groupIndex >= 0, continue);
3654 
3655     // Insert the group id in the fx
3656     gt->m_fx->getAttributes()->removeGroupId(gt->m_groupIndex);
3657     gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);
3658 
3659     gt->m_groupIndex = -1;
3660   }
3661 
3662   m_xshHandle->notifyXsheetChanged();
3663 }
3664 
3665 //======================================================
3666 
groupFxs(const std::list<TFxP> & fxs,TXsheetHandle * xshHandle)3667 void TFxCommand::groupFxs(const std::list<TFxP> &fxs,
3668                           TXsheetHandle *xshHandle) {
3669   std::unique_ptr<FxCommandUndo> undo(new UndoGroupFxs(fxs, xshHandle));
3670   if (undo->isConsistent()) {
3671     undo->redo();
3672     TUndoManager::manager()->add(undo.release());
3673   }
3674 }
3675 
3676 //**********************************************************************
3677 //    Ungroup Fxs  command
3678 //**********************************************************************
3679 
3680 class UndoUngroupFxs final : public UndoGroupFxs {
3681 public:
UndoUngroupFxs(int groupId,TXsheetHandle * xshHandle)3682   UndoUngroupFxs(int groupId, TXsheetHandle *xshHandle)
3683       : UndoGroupFxs(groupId, xshHandle) {
3684     initialize();
3685   }
3686 
redo() const3687   void redo() const override { UndoGroupFxs::undo(); }
undo() const3688   void undo() const override { UndoGroupFxs::redo(); }
3689 
getHistoryString()3690   QString getHistoryString() override { return QObject::tr("Ungroup Fx"); }
3691 
3692 private:
3693   void initialize();
3694 };
3695 
3696 //------------------------------------------------------
3697 
initialize()3698 void UndoUngroupFxs::initialize() {
3699   struct {
3700     UndoUngroupFxs *m_this;
3701 
3702     void scanFxForGroup(TFx *fx) {
3703       if (fx) {
3704         const QStack<int> &groupStack = fx->getAttributes()->getGroupIdStack();
3705 
3706         int groupIdx =
3707             groupStack.indexOf(m_this->m_groupId);  // Returns -1 if not found
3708         if (groupIdx >= 0)
3709           m_this->m_groupData.push_back(GroupData(fx, groupIdx));
3710       }
3711     }
3712 
3713   } locals = {this};
3714 
3715   TXsheet *xsh = m_xshHandle->getXsheet();
3716   FxDag *fxDag = xsh->getFxDag();
3717 
3718   // We must iterate the xsheet's fxs pool and look for fxs with the specified
3719   // group id
3720 
3721   // Search column fxs
3722   int c, cCount = xsh->getColumnCount();
3723   for (c = 0; c != cCount; ++c) {
3724     TXshColumn *column = xsh->getColumn(c);
3725     assert(column);
3726 
3727     locals.scanFxForGroup(column->getFx());
3728   }
3729 
3730   // Search normal fxs (not column ones)
3731   TFxSet *internalFxs = fxDag->getInternalFxs();
3732 
3733   int f, fCount = internalFxs->getFxCount();
3734   for (f = 0; f != fCount; ++f) {
3735     TFx *fx = internalFxs->getFx(f);
3736     locals.scanFxForGroup(fx);
3737 
3738     if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(fx)) {
3739       // Search internal macro fxs
3740       const std::vector<TFxP> &fxs = macroFx->getFxs();
3741 
3742       std::vector<TFxP>::const_iterator ft, fEnd = fxs.end();
3743       for (ft = fxs.begin(); ft != fEnd; ++ft)
3744         locals.scanFxForGroup(ft->getPointer());
3745     }
3746   }
3747 
3748   // Search output fxs
3749   int o, oCount = fxDag->getOutputFxCount();
3750   for (o = 0; o != oCount; ++o) locals.scanFxForGroup(fxDag->getOutputFx(o));
3751 }
3752 
3753 //======================================================
3754 
ungroupFxs(int groupId,TXsheetHandle * xshHandle)3755 void TFxCommand::ungroupFxs(int groupId, TXsheetHandle *xshHandle) {
3756   std::unique_ptr<FxCommandUndo> undo(new UndoUngroupFxs(groupId, xshHandle));
3757   if (undo->isConsistent()) {
3758     undo->redo();
3759     TUndoManager::manager()->add(undo.release());
3760   }
3761 }
3762 
3763 //**********************************************************************
3764 //    Rename Group  command
3765 //**********************************************************************
3766 
3767 class UndoRenameGroup final : public FxCommandUndo {
3768   std::vector<UndoGroupFxs::GroupData> m_groupData;
3769   std::wstring m_oldGroupName, m_newGroupName;
3770 
3771   TXsheetHandle *m_xshHandle;
3772 
3773 public:
UndoRenameGroup(const std::list<TFxP> & fxs,const std::wstring & newGroupName,bool fromEditor,TXsheetHandle * xshHandle)3774   UndoRenameGroup(const std::list<TFxP> &fxs, const std::wstring &newGroupName,
3775                   bool fromEditor, TXsheetHandle *xshHandle)
3776       : m_groupData(fxs.begin(), fxs.end())
3777       , m_newGroupName(newGroupName)
3778       , m_xshHandle(xshHandle) {
3779     initialize(fromEditor);
3780   }
3781 
isConsistent() const3782   bool isConsistent() const override { return !m_groupData.empty(); }
3783 
3784   void redo() const override;
3785   void undo() const override;
3786 
3787   void redo_() const;
3788 
getSize() const3789   int getSize() const override { return sizeof(*this); }
3790 
getHistoryString()3791   QString getHistoryString() override {
3792     return QObject::tr("Rename Group  : %1 > %2")
3793         .arg(QString::fromStdWString(m_oldGroupName))
3794         .arg(QString::fromStdWString(m_newGroupName));
3795   }
3796 
3797 private:
3798   void initialize(bool fromEditor);
3799 };
3800 
3801 //------------------------------------------------------
3802 
initialize(bool fromEditor)3803 void UndoRenameGroup::initialize(bool fromEditor) {
3804   struct locals {
3805     inline static bool isInvalid(const UndoGroupFxs::GroupData &gd) {
3806       return (gd.m_groupIndex < 0);
3807     }
3808   };
3809 
3810   if (!m_groupData.empty()) {
3811     m_oldGroupName =
3812         m_groupData.front().m_fx->getAttributes()->getGroupName(fromEditor);
3813 
3814     // Extract group indices
3815     std::vector<UndoGroupFxs::GroupData>::const_iterator gt,
3816         gEnd = m_groupData.end();
3817     for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
3818       const QStack<std::wstring> &groupNamesStack =
3819           gt->m_fx->getAttributes()->getGroupNameStack();
3820 
3821       gt->m_groupIndex =
3822           groupNamesStack.indexOf(m_oldGroupName);  // Returns -1 if not found
3823       assert(gt->m_groupIndex >= 0);
3824     }
3825   }
3826 
3827   m_groupData.erase(std::remove_if(m_groupData.begin(), m_groupData.end(),
3828                                    &locals::isInvalid),
3829                     m_groupData.end());
3830 }
3831 
3832 //------------------------------------------------------
3833 
redo() const3834 void UndoRenameGroup::redo() const {
3835   redo_();
3836   m_xshHandle->notifyXsheetChanged();
3837 }
3838 
3839 //------------------------------------------------------
3840 
redo_() const3841 void UndoRenameGroup::redo_() const {
3842   std::vector<UndoGroupFxs::GroupData>::const_iterator gt,
3843       gEnd = m_groupData.end();
3844   for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
3845     gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);
3846     gt->m_fx->getAttributes()->setGroupName(m_newGroupName, gt->m_groupIndex);
3847   }
3848 }
3849 
3850 //------------------------------------------------------
3851 
undo() const3852 void UndoRenameGroup::undo() const {
3853   std::vector<UndoGroupFxs::GroupData>::const_iterator gt,
3854       gEnd = m_groupData.end();
3855   for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
3856     gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);
3857     gt->m_fx->getAttributes()->setGroupName(m_oldGroupName, gt->m_groupIndex);
3858   }
3859 
3860   m_xshHandle->notifyXsheetChanged();
3861 }
3862 
3863 //======================================================
3864 
renameGroup(const std::list<TFxP> & fxs,const std::wstring & name,bool fromEditor,TXsheetHandle * xshHandle)3865 void TFxCommand::renameGroup(const std::list<TFxP> &fxs,
3866                              const std::wstring &name, bool fromEditor,
3867                              TXsheetHandle *xshHandle) {
3868   std::unique_ptr<UndoRenameGroup> undo(
3869       new UndoRenameGroup(fxs, name, fromEditor, xshHandle));
3870   if (undo->isConsistent()) {
3871     undo->redo_();  // Same schematic nodes problem as above...   :(
3872     TUndoManager::manager()->add(undo.release());
3873   }
3874 }
3875