Blob Blame Raw


#include "toonz/fxcommand.h"

// TnzLib includes
#include "toonz/txsheet.h"
#include "toonz/tcolumnfx.h"
#include "toonz/fxdag.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshpalettecolumn.h"
#include "toonz/toonzscene.h"
#include "toonz/txsheethandle.h"
#include "toonz/tfxhandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tscenehandle.h"
#include "historytypes.h"

// TnzBase includes
#include "tparamcontainer.h"
#include "tparamset.h"
#include "tfxattributes.h"
#include "tmacrofx.h"
#include "tpassivecachemanager.h"

// TnzCore includes
#include "tundo.h"
#include "tconst.h"

// Qt includes
#include <QMap>

// tcg includes
#include "tcg/tcg_macros.h"
#include "tcg/tcg_base.h"
#include "tcg/tcg_function_types.h"
#include "tcg/tcg_iterator_ops.h"

#include <memory>

/*
  Toonz currently has THREE different APIs to deal with scene objects commands:

    1. From the xsheet, see columncommand.cpp
    2. From the stage schematic
    3. From the fx schematic

  *This* is the one version that should 'rule them all' - we still have to
  unify them, though.

TODO:

  - Associated Stage Object copies when copying columns
  - Stage schematic currently ignored (eg delete columns undo)
  - Double-check macro fxs behavior
  - Double-check group behavior
  - Enforce dynamic link groups consistency
*/

//**********************************************************************
//    Local Namespace  stuff
//**********************************************************************

namespace
{

//======================================================

inline TFx *getActualIn(TFx *fx)
{
	TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx);
	return zcfx ? (assert(zcfx->getZeraryFx()), zcfx->getZeraryFx()) : fx;
}

//------------------------------------------------------

inline TFx *getActualOut(TFx *fx)
{
	TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx);
	return (zfx && zfx->getColumnFx()) ? zfx->getColumnFx() : fx;
}

//------------------------------------------------------

inline int inputPortIndex(TFx *fx, TFxPort *port)
{
	int p, pCount = fx->getInputPortCount();
	for (p = 0; p != pCount; ++p)
		if (fx->getInputPort(p) == port)
			break;
	return p;
}

//------------------------------------------------------

/*!
  Returns whether the specified fx is internal to a macro fx. Fxs
  inside a macro should not be affected by most editing commands - the
  macro is required to be exploded first.
*/
bool isInsideAMacroFx(TFx *fx, TXsheet *xsh)
{
	if (!fx)
		return false;

	TColumnFx *cfx = dynamic_cast<TColumnFx *>(fx);
	TXsheetFx *xfx = dynamic_cast<TXsheetFx *>(fx);
	TOutputFx *ofx = dynamic_cast<TOutputFx *>(fx);

	return !cfx && !xfx && !ofx &&
		   !(xsh->getFxDag()->getInternalFxs()->containsFx(fx));
}

//------------------------------------------------------

template <typename ParamCont>
void setParamsToCurrentScene(TXsheet *xsh, const ParamCont *cont)
{
	for (int p = 0; p != cont->getParamCount(); ++p) {
		TParam &param = *cont->getParam(p);

		if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(&param))
			xsh->getStageObjectTree()->setGrammar(dp);
		else if (TParamSet *paramSet = dynamic_cast<TParamSet *>(&param))
			setParamsToCurrentScene(xsh, paramSet);
	}
}

//------------------------------------------------------

inline void setFxParamToCurrentScene(TFx *fx, TXsheet *xsh)
{
	setParamsToCurrentScene(xsh, fx->getParams());
}

//------------------------------------------------------

void initializeFx(TXsheet *xsh, TFx *fx)
{
	if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
		fx = zcfx->getZeraryFx();

	xsh->getFxDag()->assignUniqueId(fx);
	setFxParamToCurrentScene(fx, xsh);
}

//------------------------------------------------------

void showFx(TXsheet *xsh, TFx *fx)
{
	fx->getAttributes()->setIsOpened(xsh->getFxDag()->getDagGridDimension() == 0);

	if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
		fx = zcfx->getZeraryFx();
	fx->getAttributes()->passiveCacheDataIdx() = -1;
}

//------------------------------------------------------

void hideFx(TXsheet *xsh, TFx *fx)
{
	if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
		fx = zcfx->getZeraryFx();
	TPassiveCacheManager::instance()->disableCache(fx);
}

//------------------------------------------------------

void addFxToCurrentScene(TFx *fx, TXsheet *xsh, bool isNewFx = true)
{
	if (isNewFx)
		initializeFx(xsh, fx);

	xsh->getFxDag()->getInternalFxs()->addFx(fx);

	showFx(xsh, fx);
}

//------------------------------------------------------

void removeFxFromCurrentScene(TFx *fx, TXsheet *xsh)
{
	xsh->getFxDag()->getInternalFxs()->removeFx(fx);
	xsh->getFxDag()->getTerminalFxs()->removeFx(fx);

	hideFx(xsh, fx);
}

} // namespace

//**********************************************************************
//    Filter Functors  definition
//**********************************************************************

namespace
{

struct FilterInsideAMacro {
	TXsheet *m_xsh;
	inline bool operator()(const TFxP &fx)
	{
		return ::isInsideAMacroFx(fx.getPointer(), m_xsh);
	}

	inline bool operator()(const TFxCommand::Link &link)
	{
		return ::isInsideAMacroFx(link.m_inputFx.getPointer(), m_xsh) ||
			   ::isInsideAMacroFx(link.m_outputFx.getPointer(), m_xsh);
	}
};

struct FilterNonTerminalFxs {
	TXsheet *xsh;
	inline bool operator()(const TFxP &fx)
	{
		return !xsh->getFxDag()->getTerminalFxs()->containsFx(fx.getPointer());
	}
};

struct FilterTerminalFxs {
	TXsheet *xsh;
	inline bool operator()(const TFxP &fx)
	{
		return xsh->getFxDag()->getTerminalFxs()->containsFx(fx.getPointer());
	}
};

struct FilterColumnFxs {
	inline bool operator()(const TFxP &fx) { return dynamic_cast<TLevelColumnFx *>(fx.getPointer()); }
};

} // namespace

//**********************************************************************
//    CloneFxFunctor  definition
//**********************************************************************

namespace
{

struct CloneFxFunctor {
	TFxP m_src;
	bool m_ownsSrc;
	TFx *operator()()
	{
		if (m_ownsSrc)
			m_ownsSrc = false; // Transfer m_src and ownership if it
		else				   // was surrendered
		{
			assert(m_src->getRefCount() > 1); // We'll be linking params to the cloned
											  // fx - so it MUST NOT be destroyed on release
			TFx *src = m_src.getPointer();
			m_src = m_src->clone(false); // Duplicate and link parameters all
			m_src->linkParams(src);		 // the following times
		}

		return m_src.getPointer();
	}
};

} // namespace

//**********************************************************************
//    FxCommandUndo  definition
//**********************************************************************

class FxCommandUndo : public TUndo
{
public:
	virtual ~FxCommandUndo() {}

	virtual bool isConsistent() const = 0;

public:
	template <typename Pred>
	static TFx *leftmostConnectedFx(TFx *fx, Pred pred);
	template <typename Pred>
	static TFx *rightmostConnectedFx(TFx *fx, Pred pred);

	static TFx *leftmostConnectedFx(TFx *fx);
	static TFx *rightmostConnectedFx(TFx *fx);

	static std::vector<TFxCommand::Link> inputLinks(TXsheet *xsh, TFx *fx);
	static std::vector<TFxCommand::Link> outputLinks(TXsheet *xsh, TFx *fx);

	int getHistoryType() { return HistoryType::Schematic; }

protected:
	static TXshZeraryFxColumn *createZeraryFxColumn(TXsheet *xsh, TFx *zfx, int row = 0);
	static void cloneGroupStack(const QStack<int> &groupIds, const QStack<std::wstring> &groupNames, TFx *toFx);
	static void cloneGroupStack(TFx *fromFx, TFx *toFx);
	static void copyGroupEditLevel(int editGroupId, TFx *toFx);
	static void copyGroupEditLevel(TFx *fromFx, TFx *toFx);
	static void copyDagPosition(TFx *fromFx, TFx *toFx);
	static void attach(TXsheet *xsh, TFx *inputFx, TFx *outputFx, int port, bool copyGroupData);
	static void attach(TXsheet *xsh, const TFxCommand::Link &link, bool copyGroupData);
	static void attachOutputs(TXsheet *xsh, TFx *insertedFx, TFx *inputFx);
	static void detachFxs(TXsheet *xsh, TFx *fxLeft, TFx *fxRight, bool detachLeft = true);
	static void insertFxs(TXsheet *xsh, const TFxCommand::Link &link, TFx *fxLeft, TFx *fxRight);
	static void insertColumn(TXsheet *xsh, TXshColumn *column, int colIdx,
							 bool removeHole = false, bool autoTerminal = false);
	static void removeFxOrColumn(TXsheet *xsh, TFx *fx, int colIdx,
								 bool insertHole = false, bool unlinkParams = true);
	static void linkParams(TFx *fx, TFx *linkedFx);
	static void unlinkParams(TFx *fx);
	static void makeNotCurrent(TFxHandle *fxHandle, TFx *fx);

private:
	static void removeColumn(TXsheet *xsh, int colIdx, bool insertHole);
	static void removeNormalFx(TXsheet *xsh, TFx *fx);
	static void removeOutputFx(TXsheet *xsh, TOutputFx *outputFx);
};

//------------------------------------------------------

TXshZeraryFxColumn *FxCommandUndo::createZeraryFxColumn(TXsheet *xsh, TFx *zfx, int row)
{
	int frameCount = xsh->getScene()->getFrameCount() - row;

	TXshZeraryFxColumn *column = new TXshZeraryFxColumn(frameCount > 0 ? frameCount : 100);
	column->getZeraryColumnFx()->setZeraryFx(zfx);
	column->insertEmptyCells(0, row);

	return column;
}

//------------------------------------------------------

void FxCommandUndo::cloneGroupStack(
	const QStack<int> &groupIds, const QStack<std::wstring> &groupNames, TFx *toFx)
{
	toFx->getAttributes()->removeFromAllGroup();

	for (int i = 0; i < groupIds.size(); ++i) {
		toFx->getAttributes()->setGroupId(groupIds[i]);
		toFx->getAttributes()->setGroupName(groupNames[i]);
	}
}

//------------------------------------------------------

void FxCommandUndo::cloneGroupStack(TFx *fromFx, TFx *toFx)
{
	if (fromFx->getAttributes()->isGrouped()) {
		cloneGroupStack(fromFx->getAttributes()->getGroupIdStack(),
						fromFx->getAttributes()->getGroupNameStack(), toFx);
	}
}

//------------------------------------------------------

void FxCommandUndo::copyGroupEditLevel(int editGroupId, TFx *toFx)
{
	toFx->getAttributes()->closeAllGroups();
	while (editGroupId != toFx->getAttributes()->getEditingGroupId() &&
		   toFx->getAttributes()->editGroup())
		;

	assert(editGroupId == toFx->getAttributes()->getEditingGroupId());
}

//------------------------------------------------------

void FxCommandUndo::copyGroupEditLevel(TFx *fromFx, TFx *toFx)
{
	assert(toFx);
	if (fromFx && fromFx->getAttributes()->isGrouped())
		copyGroupEditLevel(fromFx->getAttributes()->getEditingGroupId(), toFx);
}

//------------------------------------------------------

void FxCommandUndo::copyDagPosition(TFx *fromFx, TFx *toFx)
{
	assert(toFx);
	if (fromFx)
		toFx->getAttributes()->setDagNodePos(fromFx->getAttributes()->getDagNodePos());
}

//------------------------------------------------------

void FxCommandUndo::attach(TXsheet *xsh, TFx *inputFx, TFx *outputFx, int link,
						   bool copyGroupData)
{
	if (outputFx) {
		FxDag *fxDag = xsh->getFxDag();

		inputFx = ::getActualOut(inputFx);
		outputFx = ::getActualIn(outputFx);

		if (inputFx && link < 0) {
			assert(dynamic_cast<TXsheetFx *>(outputFx));
			fxDag->addToXsheet(inputFx);
		} else {
			int ipCount = outputFx->getInputPortCount();
			if (ipCount > 0 && link < ipCount)
				outputFx->getInputPort(link)->setFx(inputFx);

			if (copyGroupData)
				copyGroupEditLevel(inputFx, outputFx);
		}
	}
}

//------------------------------------------------------

void FxCommandUndo::attach(TXsheet *xsh, const TFxCommand::Link &link, bool copyGroupData)
{
	attach(xsh, link.m_inputFx.getPointer(), link.m_outputFx.getPointer(), link.m_index, copyGroupData);
}

//------------------------------------------------------

void FxCommandUndo::attachOutputs(TXsheet *xsh, TFx *insertedFx, TFx *inputFx)
{
	TCG_ASSERT(inputFx, return );

	FxDag *fxDag = xsh->getFxDag();

	insertedFx = ::getActualOut(insertedFx);
	inputFx = ::getActualOut(inputFx);

	int p, pCount = inputFx->getOutputConnectionCount();
	for (p = pCount - 1; p >= 0; --p)					 // Backward iteration on output connections -
	{													 // it's necessary since TFxPort::setFx() REMOVES
		TFxPort *port = inputFx->getOutputConnection(p); // the corresponding port int the output connections
		port->setFx(insertedFx);						 // container - thus, it's better to start from the end
	}

	if (fxDag->getTerminalFxs()->containsFx(inputFx)) {
		fxDag->removeFromXsheet(inputFx);
		fxDag->addToXsheet(insertedFx);
	}
}

//------------------------------------------------------

void FxCommandUndo::detachFxs(TXsheet *xsh, TFx *fxLeft, TFx *fxRight, bool detachLeft)
{
	assert(fxLeft && fxRight);

	fxLeft = ::getActualIn(fxLeft);
	fxRight = ::getActualOut(fxRight);

	int ipCount = fxLeft->getInputPortCount();

	// Redirect input/output ports
	TFx *inputFx0 = (ipCount > 0) ? fxLeft->getInputPort(0)->getFx() : 0;

	int p, opCount = fxRight->getOutputConnectionCount();
	for (p = opCount - 1; p >= 0; --p) // Backward iteration due to TFxPort::setFx()
	{
		TFxPort *outPort = fxRight->getOutputConnection(p);
		assert(outPort && outPort->getFx() == fxRight);

		outPort->setFx(inputFx0);
	}

	// Xsheet links redirection
	FxDag *fxDag = xsh->getFxDag();
	if (fxDag->getTerminalFxs()->containsFx(fxRight)) {
		fxDag->removeFromXsheet(fxRight);

		for (int p = 0; p != ipCount; ++p)
			if (TFx *inputFx = fxLeft->getInputPort(p)->getFx())
				fxDag->addToXsheet(inputFx);
	}

	if (detachLeft)
		fxLeft->disconnectAll();
}

//------------------------------------------------------

void FxCommandUndo::insertFxs(TXsheet *xsh, const TFxCommand::Link &link,
							  TFx *fxLeft, TFx *fxRight)
{
	assert(fxLeft && fxRight);

	if (link.m_inputFx && link.m_outputFx) {
		FxCommandUndo::attach(xsh, link.m_inputFx.getPointer(), fxLeft, 0, false);
		FxCommandUndo::attach(xsh, fxRight, link.m_outputFx.getPointer(), link.m_index, false);

		if (link.m_index < 0)
			xsh->getFxDag()->removeFromXsheet(::getActualOut(link.m_inputFx.getPointer()));
	}
}

//------------------------------------------------------

void FxCommandUndo::insertColumn(TXsheet *xsh, TXshColumn *column, int col,
								 bool removeHole, bool autoTerminal)
{
	FxDag *fxDag = xsh->getFxDag();
	TFx *fx = column->getFx();
	bool terminal = false;

	if (fx) {
		::showFx(xsh, fx);
		terminal = fxDag->getTerminalFxs()->containsFx(fx);
	}

	if (removeHole)
		xsh->removeColumn(col);

	xsh->insertColumn(col, column); // Attaches the fx to the xsheet, too -
									// but not if the column is a palette one.
	if (!autoTerminal) {
		// Preserve the initial terminal state.
		// This lets fxs to be linked to the xsheet while still hidden.

		fxDag->removeFromXsheet(fx);
		if (terminal)
			fxDag->addToXsheet(fx);
	}

	xsh->updateFrameCount();
}

//------------------------------------------------------

void FxCommandUndo::removeColumn(TXsheet *xsh, int col, bool insertHole)
{
	if (TFx *colFx = xsh->getColumn(col)->getFx()) {
		detachFxs(xsh, colFx, colFx);
		::hideFx(xsh, colFx);
	}

	xsh->removeColumn(col);		// Already detaches any fx in output,
	if (insertHole)				// including the terminal case
		xsh->insertColumn(col); // Note that fxs in output are not
								// removed - just detached.
	xsh->updateFrameCount();
}

//------------------------------------------------------

void FxCommandUndo::removeNormalFx(TXsheet *xsh, TFx *fx)
{
	detachFxs(xsh, fx, fx);
	::removeFxFromCurrentScene(fx, xsh); // Already hideFx()s
}

//------------------------------------------------------

void FxCommandUndo::removeOutputFx(TXsheet *xsh, TOutputFx *outputFx)
{
	detachFxs(xsh, outputFx, outputFx);
	xsh->getFxDag()->removeOutputFx(outputFx);
}

//------------------------------------------------------

void FxCommandUndo::linkParams(TFx *fx, TFx *linkedFx)
{
	if (linkedFx)
		::getActualIn(fx)->linkParams(::getActualIn(linkedFx));
}

//------------------------------------------------------

void FxCommandUndo::unlinkParams(TFx *fx)
{
	if (fx = ::getActualIn(fx), fx->getLinkedFx())
		fx->unlinkParams();
}

//------------------------------------------------------

void FxCommandUndo::makeNotCurrent(TFxHandle *fxHandle, TFx *fx)
{
	if (fx = ::getActualOut(fx), fx == fxHandle->getFx())
		fxHandle->setFx(0);
}

//------------------------------------------------------

void FxCommandUndo::removeFxOrColumn(TXsheet *xsh, TFx *fx, int colIdx,
									 bool insertHole, bool unlinkParams)
{
	assert(fx || colIdx >= 0);

	if (!fx)
		fx = xsh->getColumn(colIdx)->getFx();
	else if (TColumnFx *colFx = dynamic_cast<TColumnFx *>(fx))
		colIdx = colFx->getColumnIndex();
	else if (TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx)) {
		if (zfx->getColumnFx())
			fx = zfx->getColumnFx(), colIdx = static_cast<TColumnFx *>(fx)->getColumnIndex();
	}

	if (fx) {
		// Discriminate special fx types
		if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx)) {
			// Removed as a column
			fx = zcfx->getZeraryFx();
		} else if (TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx)) {
			assert(xsh->getFxDag()->getOutputFxCount() > 1);
			FxCommandUndo::removeOutputFx(xsh, outputFx);
		} else if (colIdx < 0)
			FxCommandUndo::removeNormalFx(xsh, fx);

		if (unlinkParams)
			FxCommandUndo::unlinkParams(fx);
	}

	if (colIdx >= 0)
		FxCommandUndo::removeColumn(xsh, colIdx, insertHole);
}

//------------------------------------------------------

template <typename Pred>
TFx *FxCommandUndo::leftmostConnectedFx(TFx *fx, Pred pred)
{
	assert(fx);

	fx = rightmostConnectedFx(fx, pred); // The rightmost fx should be discovered first,
										 // then, we'll descend from that
	do {
		fx = ::getActualIn(fx);

		if (!((fx->getInputPortCount() > 0) &&
			  fx->getInputPort(0)->isConnected() &&
			  pred(fx->getInputPort(0)->getFx())))
			break;

		fx = fx->getInputPort(0)->getFx();
	} while (true);

	return fx;
}

//------------------------------------------------------

template <typename Pred>
TFx *FxCommandUndo::rightmostConnectedFx(TFx *fx, Pred pred)
{
	assert(fx);

	do {
		fx = ::getActualOut(fx);

		if (!(fx->getOutputConnectionCount() > 0 &&
			  pred(fx->getOutputConnection(0)->getOwnerFx())))
			break;

		fx = fx->getOutputConnection(0)->getOwnerFx();
	} while (true);

	return fx;
}

//------------------------------------------------------

namespace
{
struct True_pred {
	bool operator()(TFx *fx) { return true; }
};
}

TFx *FxCommandUndo::leftmostConnectedFx(TFx *fx)
{
	return leftmostConnectedFx(fx, ::True_pred());
}

//------------------------------------------------------

TFx *FxCommandUndo::rightmostConnectedFx(TFx *fx)
{
	return rightmostConnectedFx(fx, ::True_pred());
}

//------------------------------------------------------

std::vector<TFxCommand::Link> FxCommandUndo::inputLinks(TXsheet *xsh, TFx *fx)
{
	std::vector<TFxCommand::Link> result;

	fx = ::getActualIn(fx);

	int il, ilCount = fx->getInputPortCount();
	for (il = 0; il != ilCount; ++il) {
		TFxPort *port = fx->getInputPort(il);

		assert(port);
		if (port->isConnected())
			result.push_back(TFxCommand::Link(port->getFx(), fx, il));
	}

	return result;
}

//------------------------------------------------------

std::vector<TFxCommand::Link> FxCommandUndo::outputLinks(TXsheet *xsh, TFx *fx)
{
	std::vector<TFxCommand::Link> result;

	fx = ::getActualOut(fx);

	int ol, olCount = fx->getOutputConnectionCount();
	for (ol = 0; ol != olCount; ++ol) {
		TFxPort *port = fx->getOutputConnection(ol);
		TFx *ownerFx = port->getOwnerFx();
		int portIndex = ::inputPortIndex(ownerFx, port);

		result.push_back(TFxCommand::Link(fx, ownerFx, portIndex));
	}

	FxDag *fxDag = xsh->getFxDag();
	if (fxDag->getTerminalFxs()->containsFx(fx))
		result.push_back(TFxCommand::Link(fx, fxDag->getXsheetFx(), -1));

	return result;
}

//**********************************************************************
//    Insert Fx  command
//**********************************************************************

class InsertFxUndo : public FxCommandUndo
{
	QList<TFxP> m_selectedFxs;
	QList<TFxCommand::Link> m_selectedLinks;

	TApplication *m_app;

	QList<TFxP> m_insertedFxs;
	TXshZeraryFxColumnP m_insertedColumn;
	int m_colIdx;
	bool m_columnReplacesHole;
	bool m_attachOutputs;

public:
	InsertFxUndo(const TFxP &fx, int row, int col,
				 const QList<TFxP> &selectedFxs, QList<TFxCommand::Link> selectedLinks,
				 TApplication *app, bool attachOutputs = true)
		: m_selectedFxs(selectedFxs), m_selectedLinks(selectedLinks), m_insertedColumn(0), m_app(app), m_colIdx(col), m_columnReplacesHole(false), m_attachOutputs(attachOutputs)
	{
		initialize(fx, row, col);
	}

	bool isConsistent() const { return !m_insertedFxs.isEmpty(); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

private:
	void initialize(const TFxP &newFx, int row, int col);
};

//------------------------------------------------------

inline bool has_fx_column(TFx *fx)
{
	if (TPluginInterface *plgif = dynamic_cast<TPluginInterface *>(fx))
		return plgif->isPluginZerary();
	else if (TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx))
		return zfx->isZerary();
	return false;
}

namespace
{

bool containsInputFx(const QList<TFxP> &fxs, const TFxCommand::Link &link)
{
	return fxs.contains(link.m_inputFx);
}
typedef tcg::function<bool (*)(const QList<TFxP> &, const TFxCommand::Link &), containsInputFx> ContainsInputFx_fun;

} // namespace

void InsertFxUndo::initialize(const TFxP &newFx, int row, int col)
{
	struct Locals {
		InsertFxUndo *m_this;
		inline void storeFx(TXsheet *xsh, TFx *fx)
		{
			::initializeFx(xsh, fx);
			m_this->m_insertedFxs.push_back(fx);
		}

	} locals = {this};

	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
	TFx *fx = newFx.getPointer();

	assert(!dynamic_cast<TZeraryColumnFx *>(fx));

	TZeraryFx *zfx = dynamic_cast<TZeraryFx *>(fx);
	if (has_fx_column(fx)) {
		m_insertedColumn = InsertFxUndo::createZeraryFxColumn(xsh, fx, row);

		locals.storeFx(xsh, fx);

		if (xsh->getColumn(col) && xsh->getColumn(col)->isEmpty())
			m_columnReplacesHole = true;
	} else {
		if (m_selectedFxs.isEmpty() && m_selectedLinks.isEmpty()) {
			// Attempt retrieval of current Fx from the fxHandle
			if (TFx *currentFx = m_app->getCurrentFx()->getFx())
				m_selectedFxs.push_back(currentFx);
			else {
				// Isolated case
				locals.storeFx(xsh, fx);
				return;
			}
		}

		// Remove all unacceptable input fxs
		::FilterInsideAMacro filterInMacroFxs = {xsh};
		m_selectedFxs.erase(std::remove_if(m_selectedFxs.begin(), m_selectedFxs.end(), filterInMacroFxs),
							m_selectedFxs.end());

		// Remove all unacceptable links or links whose input fx was already selected
		m_selectedLinks.erase(std::remove_if(m_selectedLinks.begin(), m_selectedLinks.end(), filterInMacroFxs),
							  m_selectedLinks.end());
		m_selectedLinks.erase(std::remove_if(m_selectedLinks.begin(), m_selectedLinks.end(),
											 tcg::bind1st(::ContainsInputFx_fun(), m_selectedFxs)),
							  m_selectedLinks.end());

		// Build an fx for each of the specified inputs
		::CloneFxFunctor cloneFx = {fx, true};

		int f, fCount = m_selectedFxs.size();
		for (f = 0; f != fCount; ++f) {
			TFx *fx = cloneFx();
			FxCommandUndo::cloneGroupStack(m_selectedFxs[f].getPointer(), fx);
			locals.storeFx(xsh, fx);
		}

		fCount = m_selectedLinks.size();
		for (f = 0; f != fCount; ++f) {
			TFx *fx = cloneFx();
			FxCommandUndo::cloneGroupStack(m_selectedLinks[f].m_inputFx.getPointer(), fx);
			locals.storeFx(xsh, fx);
		}
	}
}

//------------------------------------------------------

void InsertFxUndo::redo() const
{
	struct OnExit {
		const InsertFxUndo *m_this;
		~OnExit()
		{
			m_this->m_app->getCurrentFx()->setFx(m_this->m_insertedFxs.back().getPointer());
			m_this->m_app->getCurrentXsheet()->notifyXsheetChanged();
			m_this->m_app->getCurrentScene()->setDirtyFlag(true);
		}
	} onExit = {this};

	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();

	// Zerary case
	if (m_insertedColumn) {
		FxCommandUndo::insertColumn(
			xsh, m_insertedColumn.getPointer(), m_colIdx, m_columnReplacesHole, true);
		return;
	}

	// Isolated Fx case
	if (m_selectedLinks.isEmpty() && m_selectedFxs.isEmpty()) {
		assert(m_insertedFxs.size() == 1);
		::addFxToCurrentScene(m_insertedFxs.back().getPointer(), xsh, false); // Already showFx()s
	} else {
		// Selected links
		int i;
		for (i = 0; i < m_selectedLinks.size(); ++i) {
			const TFxCommand::Link &link = m_selectedLinks[i];
			TFx *insertedFx = m_insertedFxs[i].getPointer();

			::addFxToCurrentScene(insertedFx, xsh, false);
			FxCommandUndo::insertFxs(xsh, link, insertedFx, insertedFx);
			FxCommandUndo::copyGroupEditLevel(link.m_inputFx.getPointer(), insertedFx);
		}

		// Selected fxs
		int j, t;
		for (j = 0, t = 0; j < m_selectedFxs.size(); j++) {
			TFx *fx = m_selectedFxs[j].getPointer();
			assert(fx);

			TFx *insertedFx = m_insertedFxs[i + t].getPointer();
			t++;

			assert(insertedFx);
			::addFxToCurrentScene(insertedFx, xsh, false);

			if (m_attachOutputs)
				FxCommandUndo::attachOutputs(xsh, insertedFx, fx);

			FxCommandUndo::attach(xsh, fx, insertedFx, 0, true);
		}
	}
}

//------------------------------------------------------

void InsertFxUndo::undo() const
{
	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();

	int i, iCount = m_insertedFxs.size();
	for (i = 0; i != iCount; ++i) {
		TFx *insertedFx = m_insertedFxs[i].getPointer();

		FxCommandUndo::removeFxOrColumn(xsh, insertedFx, -1,
										m_columnReplacesHole, false); // Skip parameter links removal
		FxCommandUndo::makeNotCurrent(m_app->getCurrentFx(), insertedFx);
	}

	m_app->getCurrentFx()->setFx(0);
	m_app->getCurrentXsheet()->notifyXsheetChanged();
	m_app->getCurrentScene()->setDirtyFlag(true);
}

//------------------------------------------------------

QString InsertFxUndo::getHistoryString()
{
	QString str = (m_selectedLinks.isEmpty()) ? QObject::tr("Add Fx  : ") : QObject::tr("Insert Fx  : ");
	QList<TFxP>::iterator it;
	for (it = m_insertedFxs.begin(); it != m_insertedFxs.end(); it++) {
		if (it != m_insertedFxs.begin())
			str += QString(", ");

		str += QString::fromStdWString((*it)->getFxId());
	}
	return str;
}

//=============================================================

void TFxCommand::insertFx(TFx *newFx, const QList<TFxP> &fxs, const QList<Link> &links,
						  TApplication *app, int col, int row)
{
	if (!newFx)
		return;

	std::auto_ptr<FxCommandUndo> undo(new InsertFxUndo(newFx, row, col, fxs, links, app));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Add Fx  command
//**********************************************************************

void TFxCommand::addFx(TFx *newFx, const QList<TFxP> &fxs, TApplication *app, int col, int row)
{
	if (!newFx)
		return;

	std::auto_ptr<FxCommandUndo> undo(new InsertFxUndo(newFx, row, col, fxs, QList<Link>(), app, false));
	if (!undo->isConsistent())
		return;

	undo->redo();
	TUndoManager::manager()->add(undo.release());
}

//**********************************************************************
//    Duplicate Fx  command
//**********************************************************************

class DuplicateFxUndo : public FxCommandUndo
{
	TFxP m_fx, m_dupFx;
	TXshColumnP m_column;
	int m_colIdx;

	TXsheetHandle *m_xshHandle;
	TFxHandle *m_fxHandle;

public:
	DuplicateFxUndo(const TFxP &originalFx, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
		: m_fx(originalFx), m_colIdx(-1), m_xshHandle(xshHandle), m_fxHandle(fxHandle)
	{
		initialize();
	}

	bool isConsistent() const { return bool(m_dupFx); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

private:
	void initialize();
};

//-------------------------------------------------------------

void DuplicateFxUndo::initialize()
{
	TXsheet *xsh = m_xshHandle->getXsheet();
	TFx *fx = m_fx.getPointer();

	fx = ::getActualOut(fx);

	if (isInsideAMacroFx(fx, xsh) ||
		dynamic_cast<TXsheetFx *>(fx) || dynamic_cast<TOutputFx *>(fx) ||
		(dynamic_cast<TColumnFx *>(fx) && !dynamic_cast<TZeraryColumnFx *>(fx)))
		return;

	if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx)) {
		m_column = new TXshZeraryFxColumn(*zcfx->getColumn());
		m_colIdx = xsh->getFirstFreeColumnIndex();

		TZeraryColumnFx *dupZcfx = static_cast<TZeraryColumnFx *>(m_column->getFx());
		::initializeFx(xsh, dupZcfx->getZeraryFx());

		FxCommandUndo::cloneGroupStack(zcfx, dupZcfx);

		m_dupFx = dupZcfx;
	} else {
		fx = fx->clone(false);
		::initializeFx(xsh, fx);

		FxCommandUndo::cloneGroupStack(m_fx.getPointer(), fx);

		m_dupFx = fx;
	}
}

//-------------------------------------------------------------

void DuplicateFxUndo::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	if (m_column) {
		// Zerary Fx case
		TZeraryColumnFx *zcfx = static_cast<TZeraryColumnFx *>(m_fx.getPointer());
		TZeraryColumnFx *dupZcfx = static_cast<TZeraryColumnFx *>(m_dupFx.getPointer());

		FxCommandUndo::insertColumn(xsh, m_column.getPointer(), m_colIdx, true, true);
		FxCommandUndo::copyGroupEditLevel(zcfx, dupZcfx);

		dupZcfx->getZeraryFx()->linkParams(zcfx->getZeraryFx());
	} else {
		// Normal Fx case
		addFxToCurrentScene(m_dupFx.getPointer(), m_xshHandle->getXsheet(), false);
		FxCommandUndo::copyGroupEditLevel(m_fx.getPointer(), m_dupFx.getPointer());

		m_dupFx->linkParams(m_fx.getPointer());
	}

	m_fxHandle->setFx(m_dupFx.getPointer());
	m_xshHandle->notifyXsheetChanged();
}

//-------------------------------------------------------------

void DuplicateFxUndo::undo() const
{
	FxCommandUndo::removeFxOrColumn(
		m_xshHandle->getXsheet(), m_dupFx.getPointer(), -1, true, true);

	m_fxHandle->setFx(0);
	m_xshHandle->notifyXsheetChanged();
}

//-------------------------------------------------------------

QString DuplicateFxUndo::getHistoryString()
{
	if (TZeraryColumnFx *zDup = dynamic_cast<TZeraryColumnFx *>(m_dupFx.getPointer()))
		return QObject::tr("Create Linked Fx  : %1")
			.arg(QString::fromStdWString(zDup->getZeraryFx()->getFxId()));

	return QObject::tr("Create Linked Fx  : %1")
		.arg(QString::fromStdWString(m_dupFx->getFxId()));
}

//=============================================================

void TFxCommand::duplicateFx(TFx *src, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new DuplicateFxUndo(src, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Replace Fx  command
//**********************************************************************

class ReplaceFxUndo : public FxCommandUndo
{
	TFxP m_fx, m_repFx, m_linkedFx;
	TXshColumnP m_column, m_repColumn;
	int m_colIdx, m_repColIdx;

	std::vector<std::pair<int, TFx *>> m_inputLinks;

	TXsheetHandle *m_xshHandle;
	TFxHandle *m_fxHandle;

public:
	ReplaceFxUndo(const TFxP &replacementFx, const TFxP &replacedFx,
				  TXsheetHandle *xshHandle, TFxHandle *fxHandle)
		: m_fx(replacedFx), m_repFx(replacementFx), m_xshHandle(xshHandle), m_fxHandle(fxHandle), m_colIdx(-1), m_repColIdx(-1)
	{
		initialize();
	}

	bool isConsistent() const { return bool(m_repFx); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

private:
	void initialize();
	static void replace(TXsheet *xsh, TFx *fx, TFx *repFx,
						TXshColumn *column, TXshColumn *repColumn,
						int colIdx, int repColIdx);
};

//-------------------------------------------------------------

void ReplaceFxUndo::initialize()
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	TFx *fx = m_fx.getPointer();
	TFx *repFx = m_repFx.getPointer();

	fx = ::getActualOut(fx);

	if (isInsideAMacroFx(fx, xsh) ||
		dynamic_cast<TXsheetFx *>(fx) || dynamic_cast<TOutputFx *>(fx) ||
		(dynamic_cast<TColumnFx *>(fx) && !dynamic_cast<TZeraryColumnFx *>(fx))) {
		m_repFx = TFxP();
		return;
	}

	if (dynamic_cast<TXsheetFx *>(repFx) || dynamic_cast<TOutputFx *>(repFx) ||
		dynamic_cast<TColumnFx *>(repFx)) {
		m_repFx = TFxP();
		return;
	}

	::initializeFx(xsh, repFx);

	TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx);
	if (zcfx) {
		TXshZeraryFxColumn *zfColumn = zcfx->getColumn();

		m_column = zfColumn;
		m_colIdx = zfColumn->getIndex();

		fx = zcfx->getZeraryFx();
	}

	if (has_fx_column(fx)) {
		if (zcfx) {
			// Build a column with the same source cells pattern
			m_repColumn = new TXshZeraryFxColumn(*zcfx->getColumn());
			m_repColIdx = m_colIdx;

			// Substitute the column's zerary fx with the subsitute one
			TZeraryColumnFx *repZcfx = static_cast<TZeraryColumnFx *>(m_repColumn->getFx());
			repZcfx->setZeraryFx(repFx);

			FxCommandUndo::cloneGroupStack(zcfx, repZcfx);
			m_repFx = repZcfx;
		} else {
			m_repColumn = FxCommandUndo::createZeraryFxColumn(xsh, repFx);
			m_repColIdx = xsh->getFirstFreeColumnIndex();
			m_repFx = static_cast<TZeraryColumnFx *>(m_repColumn->getFx());
		}
	}

	FxCommandUndo::cloneGroupStack(fx, m_repFx.getPointer());

	// Store fx's input links (depending on m_repFx, they could be not matched
	// in the replacement)
	int p, ipCount = fx->getInputPortCount();
	for (p = 0; p != ipCount; ++p) {
		TFxPort *port = fx->getInputPort(p);
		if (TFx *inputFx = port->getFx())
			m_inputLinks.push_back(std::make_pair(p, inputFx));
	}

	// Store the fx's linked fx
	m_linkedFx = fx->getLinkedFx();
}

//-------------------------------------------------------------

void ReplaceFxUndo::replace(TXsheet *xsh,
							TFx *fx, TFx *repFx,
							TXshColumn *column, TXshColumn *repColumn,
							int colIdx, int repColIdx)
{
	FxDag *fxDag = xsh->getFxDag();

	TZeraryColumnFx *zcfx = column ? static_cast<TZeraryColumnFx *>(fx) : 0;
	TZeraryColumnFx *repZcfx = repColumn ? static_cast<TZeraryColumnFx *>(repFx) : 0;

	TFx *ifx = zcfx ? zcfx->getZeraryFx() : fx;
	TFx *irepFx = repZcfx ? repZcfx->getZeraryFx() : repFx;

	// Copy links first
	int p, ipCount = ifx->getInputPortCount(), ripCount = irepFx->getInputPortCount();
	for (p = 0; p != ipCount && p != ripCount; ++p) {
		TFxPort *ifxPort = ifx->getInputPort(p);
		TFxPort *irepFxPort = irepFx->getInputPort(p);

		FxCommandUndo::attach(xsh, ifxPort->getFx(), irepFx, p, true);
	}

	int opCount = fx->getOutputConnectionCount();
	for (p = opCount - 1; p >= 0; --p) {
		TFxPort *port = fx->getOutputConnection(p);
		port->setFx(repFx);
	}

	if (fxDag->getTerminalFxs()->containsFx(fx)) {
		fxDag->removeFromXsheet(fx);
		fxDag->addToXsheet(repFx);
	}

	// Remove fx/column
	FxCommandUndo::removeFxOrColumn(xsh, fx, colIdx, bool(repColumn), false);

	// Insert the new fx/column
	if (repColumn)
		FxCommandUndo::insertColumn(xsh, repColumn, repColIdx, column); // Not attached to the xsheet
	else
		::addFxToCurrentScene(repFx, xsh, false);

	FxCommandUndo::copyGroupEditLevel(fx, repFx);
	FxCommandUndo::copyDagPosition(fx, repFx);
}

//-------------------------------------------------------------

void ReplaceFxUndo::redo() const
{
	ReplaceFxUndo::replace(m_xshHandle->getXsheet(),
						   m_fx.getPointer(), m_repFx.getPointer(),
						   m_column.getPointer(), m_repColumn.getPointer(),
						   m_colIdx, m_repColIdx);
	FxCommandUndo::unlinkParams(m_fx.getPointer());

	m_fxHandle->setFx(0);
	m_xshHandle->notifyXsheetChanged();
}

//-------------------------------------------------------------

void ReplaceFxUndo::undo() const
{
	ReplaceFxUndo::replace(m_xshHandle->getXsheet(),
						   m_repFx.getPointer(), m_fx.getPointer(),
						   m_repColumn.getPointer(), m_column.getPointer(),
						   m_repColIdx, m_colIdx);

	// Repair original input links for m_fx
	m_fx->disconnectAll();

	size_t l, lCount = m_inputLinks.size();
	for (l = 0; l != lCount; ++l)
		m_fx->getInputPort(m_inputLinks[l].first)->setFx(m_inputLinks[l].second);

	// Repair parameter links
	FxCommandUndo::linkParams(m_fx.getPointer(), m_linkedFx.getPointer());

	m_fxHandle->setFx(0);
	m_xshHandle->notifyXsheetChanged();
}

//-------------------------------------------------------------

QString ReplaceFxUndo::getHistoryString()
{
	QString str = QObject::tr("Replace Fx  : ");
	str += QString("%1 > %2")
			   .arg(QString::fromStdWString(m_fx->getFxId()))
			   .arg(QString::fromStdWString(m_repFx->getFxId()));

	return str;
}

//=============================================================

void TFxCommand::replaceFx(TFx *newFx, const QList<TFxP> &fxs, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	if (!newFx)
		return;

	TUndoManager *undoManager = TUndoManager::manager();
	::CloneFxFunctor cloneFx = {newFx, true};

	undoManager->beginBlock();

	TFxP clonedFx;

	int f, fCount = fxs.size();
	for (f = 0; f != fCount; ++f) {
		if (!clonedFx)
			clonedFx = cloneFx();

		std::auto_ptr<FxCommandUndo> undo(new ReplaceFxUndo(clonedFx, fxs[f], xshHandle, fxHandle));
		if (undo->isConsistent()) {
			undo->redo();
			undoManager->add(undo.release());

			clonedFx = TFxP();
		}
	}

	undoManager->endBlock();
}

//**********************************************************************
//    Unlink Fx  command
//**********************************************************************

class UnlinkFxUndo : public FxCommandUndo
{
	TFxP m_fx, m_linkedFx;

	TXsheetHandle *m_xshHandle;

public:
	UnlinkFxUndo(const TFxP &fx, TXsheetHandle *xshHandle)
		: m_fx(fx), m_linkedFx(fx->getLinkedFx()), m_xshHandle(xshHandle) {}

	bool isConsistent() const { return bool(m_linkedFx); }

	void undo() const
	{
		FxCommandUndo::linkParams(m_fx.getPointer(), m_linkedFx.getPointer());
		m_xshHandle->notifyXsheetChanged();
	}

	void redo() const
	{
		FxCommandUndo::unlinkParams(m_fx.getPointer());
		m_xshHandle->notifyXsheetChanged();
	}

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Unlink Fx  : %1 - - %2")
			.arg(QString::fromStdWString(m_fx->getFxId()))
			.arg(QString::fromStdWString(m_linkedFx->getFxId()));
	}
};

//=============================================================

void TFxCommand::unlinkFx(TFx *fx, TFxHandle *fxHandle, TXsheetHandle *xshHandle)
{
	if (!fx)
		return;

	std::auto_ptr<FxCommandUndo> undo(new UnlinkFxUndo(fx, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Make Macro Fx  command
//**********************************************************************

class MakeMacroUndo : public FxCommandUndo
{
protected:
	TFxP m_macroFx;
	TApplication *m_app;

public:
	MakeMacroUndo(const std::vector<TFxP> &fxs, TApplication *app)
		: m_app(app)
	{
		initialize(fxs);
	}

	bool isConsistent() const { return bool(m_macroFx); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	virtual QString getHistoryString()
	{
		return QObject::tr("Make Macro Fx  : %1")
			.arg(QString::fromStdWString(m_macroFx->getFxId()));
	}

private:
	void initialize(const std::vector<TFxP> &fxs);

protected:
	MakeMacroUndo(TMacroFx *macroFx, TApplication *app)
		: m_macroFx(macroFx), m_app(app) {}
};

//-------------------------------------------------------------

void MakeMacroUndo::initialize(const std::vector<TFxP> &fxs)
{
	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();

	size_t f, fCount = fxs.size();
	for (f = 0; f != fCount; ++f) {
		// Only normal Fxs can be added in a macro
		TFx *fx = fxs[f].getPointer();

		if (isInsideAMacroFx(fx, xsh) ||
			fx->isZerary() ||
			dynamic_cast<TZeraryColumnFx *>(fx) ||
			dynamic_cast<TMacroFx *>(fx) ||
			dynamic_cast<TLevelColumnFx *>(fx) ||
			dynamic_cast<TPaletteColumnFx *>(fx) ||
			dynamic_cast<TXsheetFx *>(fx) ||
			dynamic_cast<TOutputFx *>(fx))
			return;
	}

	TMacroFx *macroFx = TMacroFx::create(fxs);
	if (!macroFx)
		return;

	::initializeFx(xsh, macroFx);
	m_macroFx = TFxP(macroFx);

	// An old comment suggested there may be trouble in case the fx editor popup is opened.
	// In any case, the new macro fx will be selected at the end - so, let's disable it right now
	m_app->getCurrentFx()->setFx(0);
}

//-------------------------------------------------------------

void MakeMacroUndo::redo() const
{
	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
	FxDag *fxDag = xsh->getFxDag();
	TFxSet *terminalFxs = fxDag->getTerminalFxs();
	TMacroFx *macroFx = static_cast<TMacroFx *>(m_macroFx.getPointer());

	::addFxToCurrentScene(macroFx, xsh, false);

	// Replace the macro's root and deal with output links
	TFx *rootFx = macroFx->getRoot();
	if (terminalFxs->containsFx(rootFx))
		fxDag->addToXsheet(macroFx);

	int p, opCount = rootFx->getOutputConnectionCount();
	for (p = opCount - 1; p >= 0; --p)
		rootFx->getOutputConnection(p)->setFx(macroFx);

	// Remove the macro's internal fxs from the scene
	const std::vector<TFxP> &fxs = macroFx->getFxs();

	size_t f, fCount = fxs.size();
	for (f = 0; f != fCount; ++f)
		::removeFxFromCurrentScene(fxs[f].getPointer(), xsh);

	// Hijack their ports (no actual redirection) - resetting the port ownership.
	// NOTE: Is this even legal? Not gonna touch it, but...   o_o!
	int ipCount = macroFx->getInputPortCount();
	for (p = 0; p != ipCount; ++p)
		macroFx->getInputPort(p)->setOwnerFx(macroFx);

	m_app->getCurrentFx()->setFx(macroFx);
	m_app->getCurrentXsheet()->notifyXsheetChanged();
}

//-------------------------------------------------------------

void MakeMacroUndo::undo() const
{
	TXsheet *xsh = m_app->getCurrentXsheet()->getXsheet();
	FxDag *fxDag = xsh->getFxDag();
	TFxSet *terminalFxs = fxDag->getTerminalFxs();
	TMacroFx *macroFx = static_cast<TMacroFx *>(m_macroFx.getPointer());

	// Reattach the macro's root to the xsheet if necessary
	TFx *rootFx = macroFx->getRoot();
	if (terminalFxs->containsFx(macroFx))
		fxDag->addToXsheet(rootFx);

	// Restore the root's output connections
	int p, opCount = macroFx->getOutputConnectionCount();
	for (p = opCount - 1; p >= 0; --p)
		macroFx->getOutputConnection(p)->setFx(rootFx);

	// Remove the macro
	::removeFxFromCurrentScene(macroFx, xsh);

	// Re-insert the macro's internal fxs and restore ports ownership
	const std::vector<TFxP> &fxs = macroFx->getFxs();

	size_t f, fCount = fxs.size();
	for (f = 0; f != fCount; ++f) {
		TFx *fx = fxs[f].getPointer();

		::addFxToCurrentScene(fx, xsh, false);

		int p, ipCount = fx->getInputPortCount();
		for (p = 0; p != ipCount; ++p)
			fx->getInputPort(p)->setOwnerFx(fx);
	}

	m_app->getCurrentFx()->setFx(0);
	m_app->getCurrentXsheet()->notifyXsheetChanged();
}

//=============================================================

void TFxCommand::makeMacroFx(const std::vector<TFxP> &fxs, TApplication *app)
{
	if (fxs.empty())
		return;

	std::auto_ptr<FxCommandUndo> undo(new MakeMacroUndo(fxs, app));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Explode Macro Fx  command
//**********************************************************************

class ExplodeMacroUndo : public MakeMacroUndo
{
public:
	ExplodeMacroUndo(TMacroFx *macro, TApplication *app)
		: MakeMacroUndo(macro, app)
	{
		initialize();
	}

	void redo() const { MakeMacroUndo::undo(); }
	void undo() const { MakeMacroUndo::redo(); }

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Explode Macro Fx  : %1")
			.arg(QString::fromStdWString(m_macroFx->getFxId()));
	}

private:
	void initialize();
};

//------------------------------------------------------

void ExplodeMacroUndo::initialize()
{
	if (!static_cast<TMacroFx *>(m_macroFx.getPointer())->getRoot())
		m_macroFx = TFxP();
}

//=============================================================

void TFxCommand::explodeMacroFx(TMacroFx *macroFx, TApplication *app)
{
	if (!macroFx)
		return;

	std::auto_ptr<FxCommandUndo> undo(new ExplodeMacroUndo(macroFx, app));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Create Output Fx  command
//**********************************************************************

class CreateOutputFxUndo : public FxCommandUndo
{
	TFxP m_outputFx;
	TXsheetHandle *m_xshHandle;

public:
	CreateOutputFxUndo(TFx *fx, TXsheetHandle *xshHandle)
		: m_outputFx(new TOutputFx), m_xshHandle(xshHandle)
	{
		initialize(fx);
	}

	bool isConsistent() const { return true; }

	void redo() const
	{
		FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();
		TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());

		fxDag->addOutputFx(outputFx);
		fxDag->setCurrentOutputFx(outputFx);

		m_xshHandle->notifyXsheetChanged();
	}

	void undo() const
	{
		TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());

		m_xshHandle->getXsheet()->getFxDag()->removeOutputFx(outputFx);
		m_xshHandle->notifyXsheetChanged();
	}

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Create Output Fx");
	}

private:
	void initialize(TFx *fx)
	{
		TXsheet *xsh = m_xshHandle->getXsheet();
		TOutputFx *outputFx = static_cast<TOutputFx *>(m_outputFx.getPointer());

		if (fx && !dynamic_cast<TOutputFx *>(fx))
			outputFx->getInputPort(0)->setFx(fx);
		else {
			TOutputFx *currentOutputFx = xsh->getFxDag()->getCurrentOutputFx();
			const TPointD &pos = currentOutputFx->getAttributes()->getDagNodePos();
			outputFx->getAttributes()->setDagNodePos(pos + TPointD(20, 20));
		}
	}
};

//=============================================================

void TFxCommand::createOutputFx(TXsheetHandle *xshHandle, TFx *currentFx)
{
	TUndo *undo = new CreateOutputFxUndo(currentFx, xshHandle);

	undo->redo();
	TUndoManager::manager()->add(undo);
}

//**********************************************************************
//    Make Output Fx Current  command
//**********************************************************************

void TFxCommand::makeOutputFxCurrent(TFx *fx, TXsheetHandle *xshHandle)
{
	TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx);
	if (!outputFx)
		return;

	TXsheet *xsh = xshHandle->getXsheet();
	if (xsh->getFxDag()->getCurrentOutputFx() == outputFx)
		return;

	xsh->getFxDag()->setCurrentOutputFx(outputFx);
	xshHandle->notifyXsheetChanged();
}

//**********************************************************************
//    Connect Nodes To Xsheet  command
//**********************************************************************

class ConnectNodesToXsheetUndo : public FxCommandUndo
{
protected:
	std::vector<TFxP> m_fxs;
	TXsheetHandle *m_xshHandle;

public:
	ConnectNodesToXsheetUndo(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
		: m_fxs(fxs.begin(), fxs.end()), m_xshHandle(xshHandle)
	{
		initialize();
	}

	bool isConsistent() const { return !m_fxs.empty(); }

	void redo() const
	{
		/*
      Due to compatibility issues from *schematicnode.cpp files, the "do" operation must be
      accessible without scene change notifications (see TFxCommand::setParent())
    */

		redo_();
		m_xshHandle->notifyXsheetChanged();
	}

	void redo_() const
	{
		FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();

		size_t f, fCount = m_fxs.size();
		for (f = 0; f != fCount; ++f)
			fxDag->addToXsheet(m_fxs[f].getPointer());
	}

	void undo() const
	{
		FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();

		size_t f, fCount = m_fxs.size();
		for (f = 0; f != fCount; ++f)
			fxDag->removeFromXsheet(m_fxs[f].getPointer());

		m_xshHandle->notifyXsheetChanged();
	}

	int getSize() const { return sizeof(*this); }

	virtual QString getHistoryString()
	{
		QString str = QObject::tr("Connect to Xsheet  : ");
		std::vector<TFxP>::iterator it;
		for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
			if (it != m_fxs.begin())
				str += QString(", ");
			str += QString::fromStdWString((*it)->getFxId());
		}
		return str;
	}

protected:
	ConnectNodesToXsheetUndo(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle, bool)
		: m_fxs(fxs.begin(), fxs.end()), m_xshHandle(xshHandle) {}

private:
	void initialize();
};

//------------------------------------------------------

void ConnectNodesToXsheetUndo::initialize()
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	::FilterInsideAMacro filterInMacro = {xsh};
	m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterInMacro),
				m_fxs.end());

	::FilterTerminalFxs filterTerminalFxs = {xsh};
	m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterTerminalFxs),
				m_fxs.end());
}

//=============================================================

void TFxCommand::connectNodesToXsheet(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new ConnectNodesToXsheetUndo(fxs, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Disconnect Nodes From Xsheet  command
//**********************************************************************

class DisconnectNodesFromXsheetUndo : public ConnectNodesToXsheetUndo
{
public:
	DisconnectNodesFromXsheetUndo(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
		: ConnectNodesToXsheetUndo(fxs, xshHandle, true) { initialize(); }

	void redo() const { ConnectNodesToXsheetUndo::undo(); }
	void undo() const { ConnectNodesToXsheetUndo::redo(); }

	QString getHistoryString()
	{
		QString str = QObject::tr("Disconnect from Xsheet  : ");
		std::vector<TFxP>::iterator it;
		for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
			if (it != m_fxs.begin())
				str += QString(", ");
			str += QString::fromStdWString((*it)->getFxId());
		}
		return str;
	}

private:
	void initialize();
};

//------------------------------------------------------

void DisconnectNodesFromXsheetUndo::initialize()
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	::FilterInsideAMacro filterInMacro = {xsh};
	m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterInMacro),
				m_fxs.end());

	::FilterNonTerminalFxs filterNonTerminalFxs = {xsh};
	m_fxs.erase(std::remove_if(m_fxs.begin(), m_fxs.end(), filterNonTerminalFxs),
				m_fxs.end());
}

//=============================================================

void TFxCommand::disconnectNodesFromXsheet(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new DisconnectNodesFromXsheetUndo(fxs, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Delete Link  command
//**********************************************************************

class DeleteLinksUndo : public FxCommandUndo
{
	struct DynamicLink {
		int m_groupIndex;
		std::string m_portName;
		TFx *m_inputFx;
	};

	typedef std::vector<DynamicLink> DynamicLinksVector;

protected:
	std::list<TFxCommand::Link> m_links; //!< The input links to remove

private:
	std::list<TFxCommand::Link> m_normalLinks;			//!< Actual *common* links from m_links
	std::list<TFx *> m_terminalFxs;						//!< Fxs connected to the xsheet (represents xsheet input links)
														//   Why SMART pointers? No fx is deleted with this command... hmm...
	std::map<TFx *, DynamicLinksVector> m_dynamicLinks; //!< Complete dynamic links configuration, per fx.

	TXsheetHandle *m_xshHandle;

public:
	DeleteLinksUndo(const std::list<TFxCommand::Link> &links, TXsheetHandle *xshHandle)
		: m_links(links), m_xshHandle(xshHandle)
	{
		initialize();
	}

	bool isConsistent() const { return !m_links.empty(); }

	void redo() const;
	void undo() const;

	int getSize() const { return 10 << 10; } // Say, around 10 kB

	virtual QString getHistoryString();

protected:
	DeleteLinksUndo(TXsheetHandle *xshHandle)
		: m_xshHandle(xshHandle) {}

	void initialize();
};

//------------------------------------------------------

void DeleteLinksUndo::initialize()
{
	struct locals {
		static bool isInvalid(FxDag *fxDag, const TFxCommand::Link &link)
		{
			if (link.m_index < 0)
				return !fxDag->getTerminalFxs()->containsFx(link.m_inputFx.getPointer());

			TFx *inFx = ::getActualOut(link.m_inputFx.getPointer());
			TFx *outFx = ::getActualIn(link.m_outputFx.getPointer());

			return (link.m_index >= outFx->getInputPortCount()) ? true : (outFx->getInputPort(link.m_index)->getFx() != inFx);
		}
	};

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Forget links dealing with an open macro Fx. Note that this INCLUDES inside/outside links.
	::FilterInsideAMacro filterInMacro = {xsh};
	m_links.erase(std::remove_if(m_links.begin(), m_links.end(), filterInMacro),
				  m_links.end());

	// Remove invalid links
	m_links.erase(std::remove_if(m_links.begin(), m_links.end(),
								 tcg::bind1st(&locals::isInvalid, fxDag)),
				  m_links.end());

	std::list<TFxCommand::Link>::iterator lt, lEnd(m_links.end());
	for (lt = m_links.begin(); lt != lEnd; ++lt) {
		const TFxCommand::Link &link = *lt;

		if (TXsheetFx *xsheetFx = dynamic_cast<TXsheetFx *>(link.m_outputFx.getPointer())) {
			// The input fx is connected to an xsheet node - ie it's terminal
			m_terminalFxs.push_back(link.m_inputFx.getPointer());
			continue;
		}

		TFx *outputFx = link.m_outputFx.getPointer();

		// Zerary columns wrap the actual zerary fx - that is the fx holding input ports
		if (TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
			outputFx = zfx->getZeraryFx();

		TFxPort *port = outputFx->getInputPort(link.m_index);

		int portGroup = port->getGroupIndex();
		if (portGroup < 0)
			m_normalLinks.push_back(link);

		if (outputFx->hasDynamicPortGroups())
			m_dynamicLinks.insert(std::make_pair(outputFx, DynamicLinksVector()));
	}

	m_normalLinks.sort(); // Really necessary?

	// Store the complete configuration of dynamic groups - not just the ones touched by
	// link editing, ALL of them.
	std::map<TFx *, DynamicLinksVector>::iterator dlt, dlEnd(m_dynamicLinks.end());
	for (dlt = m_dynamicLinks.begin(); dlt != dlEnd; ++dlt) {
		TFx *outputFx = dlt->first;
		DynamicLinksVector &dynLinks = dlt->second;

		int p, pCount = outputFx->getInputPortCount();
		for (p = 0; p != pCount; ++p) {
			TFxPort *port = outputFx->getInputPort(p);

			int g = port->getGroupIndex();
			if (g >= 0) {
				DynamicLink dLink = {g, outputFx->getInputPortName(p), port->getFx()};
				dynLinks.push_back(dLink);
			}
		}
	}
}

//------------------------------------------------------

void DeleteLinksUndo::redo() const
{
	FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();

	// Perform unlinking
	std::list<TFxCommand::Link>::const_iterator lt, lEnd(m_links.end());
	for (lt = m_links.begin(); lt != lEnd; ++lt) {
		const TFxCommand::Link &link = *lt;

		TFx *outputFx = lt->m_outputFx.getPointer();

		if (TXsheetFx *xsheetFx = dynamic_cast<TXsheetFx *>(outputFx)) {
			// Terminal fx link case
			fxDag->removeFromXsheet(link.m_inputFx.getPointer());
			continue;
		}

		// Actual link case
		if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
			outputFx = zcfx->getZeraryFx();

		int index = lt->m_index;

		assert(index < outputFx->getInputPortCount());
		if (index < outputFx->getInputPortCount())
			outputFx->getInputPort(index)->setFx(0);
	}

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void DeleteLinksUndo::undo() const
{
	FxDag *fxDag = m_xshHandle->getXsheet()->getFxDag();

	// Re-attach terminal fxs
	std::list<TFx *>::const_iterator ft;
	for (ft = m_terminalFxs.begin(); ft != m_terminalFxs.end(); ++ft) {
		if (fxDag->checkLoop(*ft, fxDag->getXsheetFx())) {
			assert(fxDag->checkLoop(*ft, fxDag->getXsheetFx()));
			continue;
		}

		fxDag->addToXsheet(*ft);
	}

	// Restore common links
	std::list<TFxCommand::Link>::const_iterator lt, lEnd(m_normalLinks.end());
	for (lt = m_normalLinks.begin(); lt != lEnd; ++lt) {
		const TFxCommand::Link &link = *lt;

		int index = link.m_index;
		TFx *inputFx = link.m_inputFx.getPointer();
		TFx *outputFx = link.m_outputFx.getPointer();

		if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(outputFx))
			outputFx = zcfx->getZeraryFx();

		if (fxDag->checkLoop(inputFx, outputFx)) {
			assert(fxDag->checkLoop(inputFx, outputFx));
			continue;
		}

		assert(index < outputFx->getInputPortCount());

		if (index < outputFx->getInputPortCount())
			outputFx->getInputPort(index)->setFx(inputFx);
	}

	// Restore complete dynamic port groups configuration
	std::map<TFx *, DynamicLinksVector>::const_iterator dlt, dlEnd(m_dynamicLinks.end());
	for (dlt = m_dynamicLinks.begin(); dlt != dlEnd; ++dlt) {
		TFx *outputFx = dlt->first;
		const DynamicLinksVector &dynLinks = dlt->second;

		{
			int g, gCount = outputFx->dynamicPortGroupsCount();
			for (g = 0; g != gCount; ++g)
				outputFx->clearDynamicPortGroup(g);
		}

		size_t d, dCount = dynLinks.size();
		for (d = 0; d != dCount; ++d) {
			const DynamicLink &link = dynLinks[d];

			TFxPort *port = new TRasterFxPort; // isAControlPort... semi-obsolete
			port->setFx(link.m_inputFx);

			outputFx->addInputPort(link.m_portName, port, link.m_groupIndex);
		}
	}

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

QString DeleteLinksUndo::getHistoryString()
{
	QString str = QObject::tr("Delete Link");
	if (!m_normalLinks.empty()) {
		str += QString("  :  ");
		std::list<TFxCommand::Link>::const_iterator it;
		for (it = m_normalLinks.begin(); it != m_normalLinks.end(); it++) {
			if (it != m_normalLinks.begin())
				str += QString(",  ");
			TFxCommand::Link boundingFxs = *it;
			str += QString("%1- -%2")
					   .arg(QString::fromStdWString(boundingFxs.m_inputFx->getName()))
					   .arg(QString::fromStdWString(boundingFxs.m_outputFx->getName()));
		}
	}

	if (!m_terminalFxs.empty()) {
		str += QString("  :  ");
		std::list<TFxP>::const_iterator it1;
		std::list<TFx *>::const_iterator ft;
		for (ft = m_terminalFxs.begin(); ft != m_terminalFxs.end(); ++ft) {
			if (ft != m_terminalFxs.begin())
				str += QString(",  ");
			str += QString("%1- -Xsheet")
					   .arg(QString::fromStdWString((*it1)->getName()));
		}
	}

	return str;
}

//=============================================================

void deleteLinks(const std::list<TFxCommand::Link> &links, TXsheetHandle *xshHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new DeleteLinksUndo(links, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//******************************************************
//    Delete Fx  command
//******************************************************

class DeleteFxOrColumnUndo : public DeleteLinksUndo
{
protected:
	TFxP m_fx;
	TXshColumnP m_column;
	int m_colIdx;

	TFxP m_linkedFx;
	std::vector<TFx *> m_nonTerminalInputs;

	mutable std::auto_ptr<TStageObjectParams>
		m_columnData;

	TXsheetHandle *m_xshHandle;
	TFxHandle *m_fxHandle;

	using DeleteLinksUndo::m_links;

public:
	DeleteFxOrColumnUndo(const TFxP &fx, TXsheetHandle *xshHandle, TFxHandle *fxHandle);
	DeleteFxOrColumnUndo(int colIdx, TXsheetHandle *xshHandle, TFxHandle *fxHandle);

	bool isConsistent() const;

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

private:
	void initialize();
};

//-------------------------------------------------------------

DeleteFxOrColumnUndo::DeleteFxOrColumnUndo(const TFxP &fx, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
	: DeleteLinksUndo(xshHandle), m_fx(fx), m_colIdx(-1), m_xshHandle(xshHandle), m_fxHandle(fxHandle)
{
	initialize();
}

//-------------------------------------------------------------

DeleteFxOrColumnUndo::DeleteFxOrColumnUndo(int colIdx, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
	: DeleteLinksUndo(xshHandle), m_colIdx(colIdx), m_xshHandle(xshHandle), m_fxHandle(fxHandle)
{
	initialize();
}

//-------------------------------------------------------------

void DeleteFxOrColumnUndo::initialize()
{
	struct {
		DeleteFxOrColumnUndo *m_this;
		inline void getActualData(TXsheet *xsh, TFx *&ifx, TFx *&ofx)
		{
			TFx *fx = m_this->m_fx.getPointer();

			if (!fx)
				fx = xsh->getColumn(m_this->m_colIdx)->getFx();

			if (fx) {
				ifx = ::getActualIn(fx);
				ofx = ::getActualOut(fx);

				if (TColumnFx *colFx = dynamic_cast<TColumnFx *>(ofx))
					m_this->m_colIdx = colFx->getColumnIndex();
			}

			m_this->m_fx = ofx;
		}
	} locals = {this};

	assert(m_fx || m_colIdx >= 0);

	TXsheet *xsh = m_xshHandle->getXsheet();
	TFx *ifx = 0, *ofx = 0;

	locals.getActualData(xsh, ifx, ofx);

	if (ofx && isInsideAMacroFx(ofx, xsh)) // Macros must be exploded first
	{
		m_fx = TFxP(), m_colIdx = -1;
		return;
	}

	// Assume shared ownership of the associated column, if any
	if (m_colIdx >= 0) {
		m_column = xsh->getColumn(m_colIdx);
		assert(m_column);

		// Currently disputed, but in the previous implementation there was code suggesting
		// that the column could have already been removed from the xsheet.
		// Preventing that case...
		if (!m_column->inColumnsSet()) {
			m_fx = TFxP(), m_colIdx = -1; // Bail out as inconsistent op
			return;						  //
		}
	} else if (TOutputFx *outputFx = dynamic_cast<TOutputFx *>(ofx)) {
		if (xsh->getFxDag()->getOutputFxCount() <= 1) {
			// Cannot delete the last output fx
			m_fx = TFxP();
			assert(m_colIdx < 0);
			return;
		}
	}

	// Store links to be re-established in the undo
	FxDag *fxDag = xsh->getFxDag();

	if (ofx) {
		// Store the terminal output link, if any
		if (fxDag->getTerminalFxs()->containsFx(ofx))
			m_links.push_back(TFxCommand::Link(ofx, fxDag->getXsheetFx(), -1));

		// Store output links
		int p, opCount = ofx->getOutputConnectionCount();
		for (p = 0; p != opCount; ++p) {
			if (TFx *outFx = ofx->getOutputConnection(p)->getOwnerFx()) {
				int ip, ipCount = outFx->getInputPortCount();
				for (ip = 0; ip != ipCount; ++ip)
					if (outFx->getInputPort(ip)->getFx() == ofx)
						break;

				assert(ip < ipCount);
				if (ip < ipCount)
					m_links.push_back(TFxCommand::Link(m_fx, outFx, ip));
			}
		}
	}

	if (ifx) {
		m_linkedFx = ifx->getLinkedFx();

		// Store input links
		int p, ipCount = ifx->getInputPortCount();
		for (p = 0; p != ipCount; ++p) {
			if (TFx *inputFx = ifx->getInputPort(p)->getFx()) {
				m_links.push_back(TFxCommand::Link(inputFx, m_fx, p));
				if (!fxDag->getTerminalFxs()->containsFx(inputFx)) // Store input fxs which DID NOT have an
					m_nonTerminalInputs.push_back(inputFx);		   // xsheet link before the deletion
			}
		}
	}

	DeleteLinksUndo::initialize();
}

//-------------------------------------------------------------

bool DeleteFxOrColumnUndo::isConsistent() const
{
	return (bool(m_fx) || (m_colIdx >= 0));

	// NOTE: DeleteLinksUndo::isConsistent() is not checked.
	//       This is because there could be no link to remove, and yet
	//       the operation IS consistent.
}

//-------------------------------------------------------------

void DeleteFxOrColumnUndo::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Store data to be restored in the undo
	if (m_colIdx >= 0) {
		assert(!m_columnData.get());

		m_columnData.reset(xsh->getStageObject(TStageObjectId::ColumnId(m_colIdx)) // Cloned, ownership acquired
							   ->getParams());									   // However, params stored there are NOT cloned.
	}																			   // This is fine since we're deleting the column...

	// Peform operation
	FxCommandUndo::removeFxOrColumn(
		xsh, m_fx.getPointer(), m_colIdx);

	m_xshHandle->notifyXsheetChanged(); // Add the rest...
}

//-------------------------------------------------------------

void DeleteFxOrColumnUndo::undo() const
{
	struct Locals {
		const DeleteFxOrColumnUndo *m_this;

		void insertColumnIn(TXsheet *xsh)
		{
			m_this->insertColumn(xsh, m_this->m_column.getPointer(),
								 m_this->m_colIdx);

			// Restore column data
			TStageObject *sObj = xsh->getStageObject(TStageObjectId::ColumnId(m_this->m_colIdx));
			assert(sObj);

			sObj->assignParams(m_this->m_columnData.get(), false);
			m_this->m_columnData.reset();
		}

	} locals = {this};

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Re-add the fx/column to the xsheet
	TFx *fx = m_fx.getPointer();

	if (m_column)
		locals.insertColumnIn(xsh);
	else if (TOutputFx *outFx = dynamic_cast<TOutputFx *>(fx))
		xsh->getFxDag()->addOutputFx(outFx);
	else
		addFxToCurrentScene(fx, xsh, false);

	if (fx) {
		// Remove xsheet connections that became terminal due to the fx
		// removal
		size_t ti, tiCount = m_nonTerminalInputs.size();
		for (ti = 0; ti != tiCount; ++ti)
			fxDag->removeFromXsheet(m_nonTerminalInputs[ti]);

		// Re-link parameters if necessary
		TFx *ifx = ::getActualIn(fx);

		if (m_linkedFx)
			ifx->linkParams(m_linkedFx.getPointer());

		// Re-establish fx links
		DeleteLinksUndo::undo();
	} else									// Already covered by DeleteLinksUndo::undo()
		m_xshHandle->notifyXsheetChanged(); // in the other branch
}

//-------------------------------------------------------------

QString DeleteFxOrColumnUndo::getHistoryString()
{
	return QObject::tr("Delete Fx Node : %1")
		.arg(QString::fromStdWString(m_fx->getFxId()));
}

//=============================================================

void deleteFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	TUndoManager *undoManager = TUndoManager::manager();
	TXsheet *xsh = xshHandle->getXsheet();

	undoManager->beginBlock();

	std::list<TFxP>::const_iterator ft, fEnd = fxs.end();
	for (ft = fxs.begin(); ft != fEnd; ++ft) {
		// Skip levels, as they are effectively supplied in here, AND in the deleteColumns() branch.
		// This should NOT be performed here, though. TO BE MOVED TO deleteSelection or ABOVE, if any.
		if (dynamic_cast<TLevelColumnFx *>(ft->getPointer()))
			continue;

		std::auto_ptr<FxCommandUndo> undo(new DeleteFxOrColumnUndo(*ft, xshHandle, fxHandle));
		if (undo->isConsistent()) {
			undo->redo();
			TUndoManager::manager()->add(undo.release());
		}
	}

	undoManager->endBlock();
}

//**********************************************************************
//    Remove Output Fx  command
//**********************************************************************

void TFxCommand::removeOutputFx(TFx *fx, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	TOutputFx *outputFx = dynamic_cast<TOutputFx *>(fx);
	if (!outputFx)
		return;

	std::auto_ptr<FxCommandUndo> undo(new DeleteFxOrColumnUndo(fx, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Delete Columns  command
//**********************************************************************

void deleteColumns(const std::list<int> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	TUndoManager *undoManager = TUndoManager::manager();

	undoManager->beginBlock();

	// As columns are deleted, their index changes. So, the easiest workaround is to address the
	// columns directly, and then their (updated) column index.
	TXsheet *xsh = xshHandle->getXsheet();

	typedef tcg::function<TXshColumn *(TXsheet::*)(int) const, &TXsheet::getColumn> getColumn_fun;
	tcg::binder1st<getColumn_fun> getCol(getColumn_fun(), *xsh);

	std::vector<TXshColumn *> cols(tcg::make_cast_it(columns.begin(), getCol),
								   tcg::make_cast_it(columns.end(), getCol));

	size_t c, cCount = cols.size();
	for (c = 0; c != cCount; ++c) {
		std::auto_ptr<FxCommandUndo> undo(new DeleteFxOrColumnUndo(cols[c]->getIndex(), xshHandle, fxHandle));
		if (undo->isConsistent()) {
			undo->redo();
			undoManager->add(undo.release());
		}
	}

	undoManager->endBlock();
}

//**********************************************************************
//    Delete Selection  command
//**********************************************************************

void TFxCommand::deleteSelection(const std::list<TFxP> &fxs,
								 const std::list<Link> &links,
								 const std::list<int> &columns,
								 TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	// Prepare selected fxs - column fxs would be done twice if the corresponding columns have
	// been supplied for deletion too
	::FilterColumnFxs filterColumnFxs;

	std::list<TFxP> filteredFxs(fxs);
	filteredFxs.erase(std::remove_if(filteredFxs.begin(), filteredFxs.end(), filterColumnFxs),
					  filteredFxs.end());

	// Perform deletions
	TUndoManager::manager()->beginBlock();

	deleteColumns(columns, xshHandle, fxHandle);
	deleteFxs(filteredFxs, xshHandle, fxHandle);
	deleteLinks(links, xshHandle);

	TUndoManager::manager()->endBlock();
}

//**********************************************************************
//    Paste Fxs  command
//**********************************************************************

/*
  NOTE: Zerary fxs should not be in the fxs list - but rather in the columns list.
  This would allow us to forget about the zeraryColumnSize map.

  This requires changing the *selection*, though. To be done, in a later step.
*/

class UndoPasteFxs : public FxCommandUndo
{
protected:
	std::list<TFxP> m_fxs;				   //!< Fxs to be pasted
	std::list<TXshColumnP> m_columns;	  //!< Columns to be pasted
	std::vector<TFxCommand::Link> m_links; //!< Links to be re-established at the redo

	TXsheetHandle *m_xshHandle;
	TFxHandle *m_fxHandle;

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)
		: m_fxs(fxs), m_columns(columns), m_xshHandle(xshHandle), m_fxHandle(fxHandle)
	{
		initialize(zeraryFxColumnSize, pos, addOffset);
	}

	bool isConsistent() const { return !(m_fxs.empty() && m_columns.empty()); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

protected:
	template <typename Func>
	void for_each_fx(Func func) const;

private:
	void initialize(const std::map<TFx *, int> &zeraryFxColumnSize, const TPointD &pos, bool addOffset);
};

//------------------------------------------------------

void UndoPasteFxs::initialize(const std::map<TFx *, int> &zeraryFxColumnSize,
							  const TPointD &pos, bool addOffset)
{
	struct locals {
		static void buildDagPos(TFx *fromFx, TFx *toFx, bool copyPos, bool addOffset)
		{
			TPointD dagPos = TConst::nowhere;
			if (copyPos) {
				static const TPointD offset(30, 30);

				dagPos = fromFx->getAttributes()->getDagNodePos();
				if (addOffset && (dagPos != TConst::nowhere)) // Shift may only happen if the copied
					dagPos += offset;						  // position is well-formed.
			}

			toFx->getAttributes()->setDagNodePos(dagPos);
		}

		static void renamePort(TFx *fx, int p, const std::wstring &oldId, const std::wstring &newId)
		{
			const QString &qOldId = QString::fromStdWString(oldId);
			const QString &qNewId = QString::fromStdWString(newId);
			const QString &qPortName = QString::fromStdString(fx->getInputPortName(p));

			if (qPortName.endsWith(qOldId)) {
				QString qNewPortName = qPortName;
				qNewPortName.replace(qOldId, qNewId);
				fx->renamePort(qPortName.toStdString(), qNewPortName.toStdString());
			}
		}

		static bool circularSubxsheet(TXsheet *xsh, const TXshColumnP &col)
		{
			return xsh->checkCircularReferences(col.getPointer());
		}

		static void push_back(std::vector<TFx *> &fxs, TFx *fx) { fxs.push_back(fx); }
	};

	TXsheet *xsh = m_xshHandle->getXsheet();
	bool copyDagPos = (pos != TConst::nowhere);

	// Initialize fxs
	std::list<TFxP>::iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd;) {
		TFx *fx = ft->getPointer();

		assert(!dynamic_cast<TZeraryColumnFx *>(fx));

		if (has_fx_column(fx)) {
			// Zerary case

			// Since we have no column available (WHICH IS WRONG), we'll build up a column with
			// the specified column size
			std::map<TFx *, int>::const_iterator it = zeraryFxColumnSize.find(fx);
			int rows = (it == zeraryFxColumnSize.end()) ? 100 : it->second;

			TXshZeraryFxColumn *column = new TXshZeraryFxColumn(rows);
			TZeraryColumnFx *zcfx = column->getZeraryColumnFx();

			zcfx->setZeraryFx(fx);

			int op, opCount = fx->getOutputConnectionCount();
			for (op = opCount - 1; op >= 0; --op)				// Output links in the zerary case were
			{													// assigned to the *actual* zerary
				TFxPort *outPort = fx->getOutputConnection(op); // and not to a related zerary column.
				outPort->setFx(zcfx);							// This is an FxsData fault... BUGFIX REQUEST!
			}

			zcfx->getAttributes()->setDagNodePos(fx->getAttributes()->getDagNodePos());
			m_columns.push_front(column);

			ft = m_fxs.erase(ft);
			continue;
		}

		// Macro case
		if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(fx)) {
			const std::vector<TFxP> &inMacroFxs = macroFx->getFxs();

			size_t f, fCount = inMacroFxs.size();
			for (f = 0; f != fCount; ++f) {
				TFx *inFx = inMacroFxs[f].getPointer();
				const std::wstring &oldFxId = inFx->getFxId();

				// Since we're pasting a macro, we're actually pasting all of its inner fxs,
				// which must have a different name (id) from the originals. However, such ids
				// are actually copied in the macro's *port names* - which must therefore be
				// redirected to the new names.

				::initializeFx(xsh, inFx);
				const std::wstring &newFxId = inFx->getFxId();

				int ip, ipCount = macroFx->getInputPortCount();
				for (ip = 0; ip != ipCount; ++ip)
					locals::renamePort(macroFx, ip, oldFxId, newFxId);
			}
		}

		::initializeFx(xsh, fx);
		locals::buildDagPos(fx, fx, copyDagPos, addOffset);

		++ft;
	}

	// Filter columns
	m_columns.erase(std::remove_if(m_columns.begin(), m_columns.end(), tcg::bind1st(&locals::circularSubxsheet, xsh)),
					m_columns.end());

	// Initialize columns
	std::list<TXshColumnP>::const_iterator ct, cEnd(m_columns.end());
	for (ct = m_columns.begin(); ct != cEnd; ++ct) {
		if (TFx *cfx = (*ct)->getFx()) {
			::initializeFx(xsh, cfx);
			locals::buildDagPos(cfx, cfx, copyDagPos, addOffset);
		}
	}

	// Now, let's make a temporary container of all the stored fxs, both
	// normal and column-based
	std::vector<TFx *> fxs;
	fxs.reserve(m_fxs.size() + m_columns.size());

	for_each_fx(tcg::bind1st(&locals::push_back, fxs));

	// We need to store input links for these fxs
	size_t f, fCount = fxs.size();
	for (f = 0; f != fCount; ++f) {
		TFx *fx = fxs[f];

		TFx *ofx = ::getActualIn(fx);
		fx = ::getActualOut(fx);

		int il, ilCount = ofx->getInputPortCount();
		for (il = 0; il != ilCount; ++il) {
			if (TFx *ifx = ofx->getInputPort(il)->getFx())
				m_links.push_back(TFxCommand::Link(ifx, ofx, il));
		}
	}

	// Apply the required position, if any
	if (pos != TConst::nowhere) {
		// Then, we'll take the mean difference from pos and add it to
		// each fx
		TPointD middlePos;
		int fxsCount = 0;

		std::vector<TFx *>::const_iterator ft, fEnd = fxs.end();
		for (ft = fxs.begin(); ft != fEnd; ++ft) {
			TFx *fx = *ft;

			const TPointD &fxPos = fx->getAttributes()->getDagNodePos();
			if (fxPos != TConst::nowhere) {
				middlePos += fxPos;
				++fxsCount;
			}
		}

		if (fxsCount > 0) {
			middlePos = TPointD(middlePos.x / fxsCount, middlePos.y / fxsCount);
			const TPointD &offset = pos - middlePos;

			for (ft = fxs.begin(); ft != fEnd; ++ft) {
				TFx *fx = *ft;

				const TPointD &fxPos = fx->getAttributes()->getDagNodePos();
				fx->getAttributes()->setDagNodePos(fxPos + offset);
			}
		}
	}
}

//------------------------------------------------------

void UndoPasteFxs::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Iterate all normal fxs
	std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
		// Re-insert them in the scene
		addFxToCurrentScene(ft->getPointer(), xsh, false);
	}

	// Iterate columns
	std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
	for (ct = m_columns.begin(); ct != cEnd; ++ct) {
		// Insert them
		FxCommandUndo::insertColumn(
			xsh, ct->getPointer(), xsh->getFirstFreeColumnIndex(),
			true, false);
	}

	// Restore links
	size_t l, lCount = m_links.size();
	for (l = 0; l != lCount; ++l)
		FxCommandUndo::attach(xsh, m_links[l], false);

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void UndoPasteFxs::undo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
		TFx *fx = ft->getPointer();

		FxCommandUndo::removeFxOrColumn(xsh, fx, -1,
										true, false); // Skip parameter links removal
		FxCommandUndo::makeNotCurrent(m_fxHandle, fx);
	}

	std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
	for (ct = m_columns.begin(); ct != cEnd; ++ct) {
		FxCommandUndo::removeFxOrColumn(xsh, 0, (*ct)->getIndex(),
										true, false); // Skip parameter links removal
		FxCommandUndo::makeNotCurrent(m_fxHandle, (*ct)->getFx());
	}

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

template <typename Func>
void UndoPasteFxs::for_each_fx(Func func) const
{
	std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd; ++ft)
		func(ft->getPointer());

	std::list<TXshColumnP>::const_iterator ct, cEnd = m_columns.end();
	for (ct = m_columns.begin(); ct != cEnd; ++ct)
		if (TFx *cfx = (*ct)->getFx())
			func(cfx);
}

//------------------------------------------------------

QString UndoPasteFxs::getHistoryString()
{
	QString str = QObject::tr("Paste Fx  :  ");
	std::list<TFxP>::const_iterator it;
	for (it = m_fxs.begin(); it != m_fxs.end(); it++) {
		if (it != m_fxs.begin())
			str += QString(",  ");
		str += QString("%1")
				   .arg(QString::fromStdWString((*it)->getName()));
	}
	return str;
}

//=============================================================

void TFxCommand::pasteFxs(const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
						  const std::list<TXshColumnP> &columns, const TPointD &pos,
						  TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoPasteFxs(
		fxs, zeraryFxColumnSize, columns, pos, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Add Paste Fxs  command
//**********************************************************************

class UndoAddPasteFxs : public UndoPasteFxs
{
protected:
	TFxCommand::Link m_linkIn; //!< Input link to be re-established on redo

public:
	UndoAddPasteFxs(TFx *inFx, const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
					const std::list<TXshColumnP> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
		: UndoPasteFxs(fxs, zeraryFxColumnSize, columns, TConst::nowhere,
					   xshHandle, fxHandle)
	{
		initialize(inFx);
	}

	void redo() const;

	int getSize() const { return sizeof(*this); }

private:
	void initialize(TFx *inFx);
};

//------------------------------------------------------

void UndoAddPasteFxs::initialize(TFx *inFx)
{
	if (!(inFx && UndoPasteFxs::isConsistent()))
		return;

	// NOTE: Any zerary (shouldn't be there anyway) has already been
	//       moved to columns at this point

	TXsheet *xsh = m_xshHandle->getXsheet();

	// Ensure that inFx and outFx are not in a macro Fx
	if (::isInsideAMacroFx(inFx, xsh)) {
		m_fxs.clear(), m_columns.clear();
		return;
	}

	// Get the first fx to be inserted, and follow links down
	// (until an empty input port at index 0 is found)
	TFx *ifx = FxCommandUndo::leftmostConnectedFx(m_fxs.front().getPointer());

	// Then, we have the link to be established
	m_linkIn = TFxCommand::Link(inFx, ifx, 0);

	// Furthermore, clone the group stack from inFx into each inserted fx
	typedef tcg::function<void (*)(TFx *, TFx *), FxCommandUndo::cloneGroupStack> clone_fun;
	for_each_fx(tcg::bind1st(clone_fun(), inFx));
}

//------------------------------------------------------

void UndoAddPasteFxs::redo() const
{
	if (m_linkIn.m_inputFx) {
		TXsheet *xsh = m_xshHandle->getXsheet();

		// Further apply the stored link
		FxCommandUndo::attach(xsh, m_linkIn, false);

		// Copiare l'indice di gruppo dell'fx di input
		typedef tcg::function<void (*)(TFx *, TFx *), FxCommandUndo::copyGroupEditLevel> copy_fun;
		for_each_fx(tcg::binder1st<copy_fun>(copy_fun(), m_linkIn.m_inputFx.getPointer()));
	}

	UndoPasteFxs::redo();
}

//=============================================================

void TFxCommand::addPasteFxs(TFx *inFx, const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
							 const std::list<TXshColumnP> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoAddPasteFxs(
		inFx, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		// NOTE: (inFx == 0) is considered consistent, as long as UndoPasteFxs::isConsistent()
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Insert Paste Fxs  command
//**********************************************************************

class UndoInsertPasteFxs : public UndoAddPasteFxs
{
	TFxCommand::Link m_linkOut; //!< Output link to be re-established
								//!< on redo
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)
		: UndoAddPasteFxs(link.m_inputFx.getPointer(), fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle)
	{
		initialize(link);
	}

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

private:
	void initialize(const TFxCommand::Link &link);
};

//------------------------------------------------------

void UndoInsertPasteFxs::initialize(const TFxCommand::Link &link)
{
	if (!UndoPasteFxs::isConsistent())
		return;

	TXsheet *xsh = m_xshHandle->getXsheet();
	TFx *inFx = link.m_inputFx.getPointer();
	TFx *outFx = link.m_outputFx.getPointer();

	// Ensure consistency
	if (!(inFx && outFx) || ::isInsideAMacroFx(outFx, xsh)) {
		m_fxs.clear(), m_columns.clear();
		return;
	}

	// Get the first fx to be inserted, and follow links up
	// (to a no output fx)
	TFx *ofx = FxCommandUndo::rightmostConnectedFx(m_fxs.front().getPointer());

	// Now, store the appropriate output link
	m_linkOut = TFxCommand::Link(ofx, outFx, link.m_index);
}

//------------------------------------------------------

void UndoInsertPasteFxs::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Further apply the stored link pair
	FxCommandUndo::attach(xsh, m_linkOut, false);

	if (m_linkOut.m_index < 0)
		xsh->getFxDag()->removeFromXsheet(m_linkIn.m_inputFx.getPointer());

	UndoAddPasteFxs::redo();
}

//------------------------------------------------------

void UndoInsertPasteFxs::undo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Reattach the original configuration
	TFxCommand::Link orig(m_linkIn.m_inputFx, m_linkOut.m_outputFx, m_linkOut.m_index);
	FxCommandUndo::attach(xsh, orig, false);

	UndoAddPasteFxs::undo();
}

//=============================================================

void TFxCommand::insertPasteFxs(
	const Link &link, const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
	const std::list<TXshColumnP> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoInsertPasteFxs(
		link, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Replace Paste Fxs  command
//**********************************************************************

class UndoReplacePasteFxs : public UndoAddPasteFxs
{
	std::auto_ptr<DeleteFxOrColumnUndo> m_deleteFxUndo;

	TFx *m_fx, *m_rightmostFx;

public:
	UndoReplacePasteFxs(TFx *fx, const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
						const std::list<TXshColumnP> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
		: UndoAddPasteFxs(inFx(fx), fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle), m_deleteFxUndo(new DeleteFxOrColumnUndo(fx, xshHandle, fxHandle)), m_fx(fx), m_rightmostFx()
	{
		initialize();
	}

	bool isConsistent() const { return UndoAddPasteFxs::isConsistent() &&
									   m_deleteFxUndo->isConsistent(); }
	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

private:
	static TFx *inFx(const TFx *fx)
	{
		return (fx && fx->getInputPortCount() > 0) ? fx->getInputPort(0)->getFx() : 0;
	}

	void initialize();
};

//------------------------------------------------------

void UndoReplacePasteFxs::initialize()
{
	if (m_fxs.empty())
		return;

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Get the first fx to be inserted, and follow links up
	// (to a no output fx)
	m_rightmostFx = FxCommandUndo::rightmostConnectedFx(this->m_fxs.front().getPointer());

	// Then, add to the list of links to be inserted upon redo
	int ol, olCount = m_fx->getOutputConnectionCount();
	for (ol = 0; ol != olCount; ++ol) {
		TFxPort *port = m_fx->getOutputConnection(ol);
		TFx *ownerFx = port->getOwnerFx();

		TCG_ASSERT(port && ownerFx, continue);

		int p = ::inputPortIndex(ownerFx, port);
		TCG_ASSERT(p < ownerFx->getInputPortCount(), continue);

		this->m_links.push_back(TFxCommand::Link(m_rightmostFx, ownerFx, p));
	}

	if (fxDag->getTerminalFxs()->containsFx(m_fx))
		this->m_links.push_back(TFxCommand::Link(m_rightmostFx, fxDag->getXsheetFx(), -1));
}

//------------------------------------------------------

void UndoReplacePasteFxs::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Deleting m_fx would attach its input to the xsheet, if m_fx is terminal.
	// In our case, however, it needs to be attached to the replacement fx - so,
	// let's detach m_fx from the xsheet
	fxDag->removeFromXsheet(m_fx);

	// Then, delete the fx and insert the replacement. Note that this order is
	// required to ensure the correct dag positions
	m_deleteFxUndo->redo();
	UndoAddPasteFxs::redo();
}

//------------------------------------------------------

void UndoReplacePasteFxs::undo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Remove m_lastFx's output connections - UndoAddPasteFxs would try to
	// redirect them to the replaced fx's input (due to the 'blind' detach command)
	int ol, olCount = m_rightmostFx->getOutputConnectionCount();
	for (ol = olCount - 1; ol >= 0; --ol)
		if (TFxPort *port = m_rightmostFx->getOutputConnection(ol))
			port->setFx(0);

	fxDag->removeFromXsheet(m_rightmostFx);

	// Reverse the applied commands. Again, the order prevents 'bumped' dag positions

	UndoAddPasteFxs::undo(); // This would bridge the inserted fxs' inputs with their outputs
	m_deleteFxUndo->undo();  // This also re-establishes the original output links
}

//=============================================================

void TFxCommand::replacePasteFxs(TFx *inFx, const std::list<TFxP> &fxs, const std::map<TFx *, int> &zeraryFxColumnSize,
								 const std::list<TXshColumnP> &columns, TXsheetHandle *xshHandle, TFxHandle *fxHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoReplacePasteFxs(inFx, fxs, zeraryFxColumnSize, columns, xshHandle, fxHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Disconnect Fxs  command
//**********************************************************************

class UndoDisconnectFxs : public FxCommandUndo
{
protected:
	std::list<TFxP> m_fxs;
	TFx *m_leftFx, *m_rightFx;

	// NOTE: Although we'll detach only 1 input link, fxs with dynamic ports still require us
	// to store the whole ports configuration
	std::vector<TFxCommand::Link> m_undoLinksIn, m_undoLinksOut, m_undoTerminalLinks;
	std::vector<QPair<TFxP, TPointD>> m_undoDagPos, m_redoDagPos;

	TXsheetHandle *m_xshHandle;

public:
	UndoDisconnectFxs(const std::list<TFxP> &fxs, const QList<QPair<TFxP, TPointD>> &oldFxPos,
					  TXsheetHandle *xshHandle)
		: m_fxs(fxs), m_xshHandle(xshHandle), m_undoDagPos(oldFxPos.begin(), oldFxPos.end())
	{
		initialize();
	}

	bool isConsistent() const { return !m_fxs.empty(); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Disconnect Fx");
	}

private:
	void initialize();

	static void applyPos(const QPair<TFxP, TPointD> &pair)
	{
		pair.first->getAttributes()->setDagNodePos(pair.second);
	}

	static void attach(TXsheet *xsh, const TFxCommand::Link &link)
	{
		FxCommandUndo::attach(xsh, link, false);
	}
	static void detachXsh(TXsheet *xsh, const TFxCommand::Link &link)
	{
		xsh->getFxDag()->removeFromXsheet(link.m_inputFx.getPointer());
	}
};

//======================================================

void UndoDisconnectFxs::initialize()
{
	struct locals {
		static QPair<TFxP, TPointD> originalPos(const QPair<TFxP, TPointD> &pair)
		{
			return QPair<TFxP, TPointD>(pair.first, pair.first->getAttributes()->getDagNodePos());
		}

		static bool contains(const std::list<TFxP> &fxs, TFx *fx)
		{
			tcg::function<TFx *(TFxP::*)() const, &TFxP::getPointer> getPointer_fun;

			return (std::count(tcg::make_cast_it(fxs.begin(), getPointer_fun),
							   tcg::make_cast_it(fxs.end(), getPointer_fun), fx) > 0);
		}
	};

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Don't deal with fxs inside a macro fx
	::FilterInsideAMacro insideAMacro_fun = {xsh};
	if (std::find_if(m_fxs.begin(), m_fxs.end(), insideAMacro_fun) != m_fxs.end())
		m_fxs.clear();

	if (m_fxs.empty())
		return;

	// Build fxs data
	tcg::binder1st<bool (*)(const std::list<TFxP> &, TFx *)> contains_fun(&locals::contains, m_fxs);

	m_leftFx = FxCommandUndo::leftmostConnectedFx(m_fxs.front().getPointer(), contains_fun);
	m_rightFx = FxCommandUndo::rightmostConnectedFx(m_fxs.front().getPointer(), contains_fun);

	// Store sensible original data for the undo
	m_undoLinksIn = FxCommandUndo::inputLinks(xsh, m_leftFx);
	m_undoLinksOut = FxCommandUndo::outputLinks(xsh, m_rightFx);

	std::vector<TFxCommand::Link>::const_iterator lt, lEnd = m_undoLinksIn.end();
	for (lt = m_undoLinksIn.begin(); lt != lEnd; ++lt)
		if (fxDag->getTerminalFxs()->containsFx(lt->m_inputFx.getPointer()))
			m_undoTerminalLinks.push_back(TFxCommand::Link(lt->m_inputFx.getPointer(), fxDag->getXsheetFx(), -1));

	std::vector<QPair<TFxP, TPointD>>(
		tcg::make_cast_it(m_undoDagPos.begin(), &locals::originalPos),
		tcg::make_cast_it(m_undoDagPos.end(), &locals::originalPos))
		.swap(m_redoDagPos);
}

//------------------------------------------------------

void UndoDisconnectFxs::redo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Detach the first port only - I'm not sure it should really be like this, but it's
	// legacy, and altering it would require to revise the 'simulation' procedures
	// in fxschematicscene.cpp...  (any command simulation should be done here, btw)
	FxCommandUndo::detachFxs(xsh, m_leftFx, m_rightFx, false);
	if (m_leftFx->getInputPortCount() > 0)
		m_leftFx->getInputPort(0)->setFx(0);

	// This is also convenient, since fxs with dynamic groups will maintain all BUT 1
	// port - thus preventing us from dealing with that, since 1 port is always available
	// on fxs with dynamic ports...

	std::for_each(m_redoDagPos.begin(), m_redoDagPos.end(), applyPos);

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void UndoDisconnectFxs::undo() const
{
	typedef void (*LinkFun)(TXsheet *xsh, const TFxCommand::Link &);

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Restore the old links
	tcg::binder1st<LinkFun> attacher(&UndoDisconnectFxs::attach, xsh);
	tcg::binder1st<LinkFun> xshDetacher(&UndoDisconnectFxs::detachXsh, xsh);

	std::for_each(m_undoLinksIn.begin(), m_undoLinksIn.end(), attacher);
	std::for_each(m_undoLinksOut.begin(), m_undoLinksOut.end(), attacher);

	std::for_each(m_undoLinksIn.begin(), m_undoLinksIn.end(), xshDetacher);
	std::for_each(m_undoTerminalLinks.begin(), m_undoTerminalLinks.end(), attacher);

	// Restore old positions
	std::for_each(m_undoDagPos.begin(), m_undoDagPos.end(), applyPos);

	m_xshHandle->notifyXsheetChanged();
}

//======================================================

void TFxCommand::disconnectFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle,
							   const QList<QPair<TFxP, TPointD>> &fxPos)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoDisconnectFxs(fxs, fxPos, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Connect Fxs  command
//**********************************************************************

class UndoConnectFxs : public UndoDisconnectFxs
{
	struct GroupData;

private:
	TFxCommand::Link m_link;
	std::vector<GroupData> m_undoGroupDatas;

public:
	UndoConnectFxs(const TFxCommand::Link &link, const std::list<TFxP> &fxs,
				   const QList<QPair<TFxP, TPointD>> &fxPos, TXsheetHandle *xshHandle)
		: UndoDisconnectFxs(fxs, fxPos, xshHandle), m_link(link)
	{
		initialize();
	}

	bool isConsistent() const { return !m_fxs.empty(); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString();

private:
	void initialize();

	static void applyPos(const QPair<TFxP, TPointD> &pair)
	{
		pair.first->getAttributes()->setDagNodePos(pair.second);
	}
};

//======================================================

struct UndoConnectFxs::GroupData {
	TFx *m_fx;
	QStack<int> m_groupIds;
	QStack<std::wstring> m_groupNames;
	int m_editingGroup;

public:
	GroupData(TFx *fx);
	void restore() const;
};

//------------------------------------------------------

UndoConnectFxs::GroupData::GroupData(TFx *fx)
	: m_fx(fx), m_groupIds(fx->getAttributes()->getGroupIdStack()), m_groupNames(fx->getAttributes()->getGroupNameStack()), m_editingGroup(fx->getAttributes()->getEditingGroupId())
{
}

//------------------------------------------------------

void UndoConnectFxs::GroupData::restore() const
{
	assert(!m_groupIds.empty());

	FxCommandUndo::cloneGroupStack(m_groupIds, m_groupNames, m_fx);
	FxCommandUndo::copyGroupEditLevel(m_editingGroup, m_fx);
}

//======================================================

void UndoConnectFxs::initialize()
{
	if (!UndoDisconnectFxs::isConsistent())
		return;

	TCG_ASSERT(m_link.m_inputFx && m_link.m_outputFx,
			   m_fxs.clear();
			   return );

	// Store sensible original data for the undo
	m_undoGroupDatas.reserve(m_fxs.size());

	std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
		if ((*ft)->getAttributes()->isGrouped())
			m_undoGroupDatas.push_back(GroupData((*ft).getPointer()));
	}
}

//------------------------------------------------------

void UndoConnectFxs::redo() const
{
	UndoDisconnectFxs::redo();

	TXsheet *xsh = m_xshHandle->getXsheet();

	// Apply the new links
	FxCommandUndo::insertFxs(xsh, m_link, m_leftFx, m_rightFx);

	// Copy the input fx's group data
	TFx *inFx = m_link.m_inputFx.getPointer();

	std::list<TFxP>::const_iterator ft, fEnd = m_fxs.end();
	for (ft = m_fxs.begin(); ft != fEnd; ++ft) {
		TFx *fx = (*ft).getPointer();

		FxCommandUndo::cloneGroupStack(inFx, fx);
		FxCommandUndo::copyGroupEditLevel(inFx, fx);
	}

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void UndoConnectFxs::undo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	// Undo the insert
	FxCommandUndo::detachFxs(xsh, m_leftFx, m_rightFx);
	FxCommandUndo::attach(xsh, m_link, false);

	// Restore the old fxs' group data
	tcg::function<void (GroupData::*)() const, &GroupData::restore> restore_fun;
	std::for_each(m_undoGroupDatas.begin(), m_undoGroupDatas.end(), restore_fun);

	UndoDisconnectFxs::undo();
}

//------------------------------------------------------

QString UndoConnectFxs::getHistoryString()
{
	return QObject::tr("Connect Fx : %1 - %2")
		.arg(QString::fromStdWString(m_leftFx->getName()))
		.arg(QString::fromStdWString(m_rightFx->getName()));
}

//======================================================

void TFxCommand::connectFxs(const Link &link, const std::list<TFxP> &fxs, TXsheetHandle *xshHandle,
							const QList<QPair<TFxP, TPointD>> &fxPos)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoConnectFxs(link, fxs, fxPos, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Set Parent  command
//**********************************************************************

class SetParentUndo : public FxCommandUndo
{
	TFxP m_oldFx, m_newFx, m_parentFx;
	int m_parentPort;

	bool m_removeFromXsheet;

	TXsheetHandle *m_xshHandle;

public:
	SetParentUndo(TFx *fx, TFx *parentFx, int parentFxPort, TXsheetHandle *xshHandle)
		: m_newFx(fx), m_parentFx(parentFx), m_parentPort(parentFxPort), m_xshHandle(xshHandle)
	{
		initialize();
	}

	bool isConsistent() const { return m_parentFx; }

	void redo() const;
	void redo_() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

private:
	void initialize();
};

//------------------------------------------------------

void SetParentUndo::initialize()
{
	if (!m_parentFx)
		return;

	// NOTE: We cannot store this directly, since it's the actual out that owns
	// the actual in, not viceversa
	TFx *parentFx = ::getActualIn(m_parentFx.getPointer());

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	assert(m_parentPort < parentFx->getInputPortCount());
	assert(m_parentPort >= 0);

	m_oldFx = parentFx->getInputPort(m_parentPort)->getFx();

	m_removeFromXsheet =											  // This is a bit odd. The legacy behavior of the
		(m_newFx &&													  // setParent() (ie connect 2 fxs with a link, I know
		 (m_newFx->getOutputConnectionCount() == 0) &&				  // the name is bad) command is that of *removing terminal
		 fxDag->getTerminalFxs()->containsFx(m_newFx.getPointer()) && // links* on the input fx (m_newFx in our case).
		 m_parentFx != fxDag->getXsheetFx());						  // I've retained this behavior since it can be handy
																	  // for users, but only if the xsheet link is the SOLE output
	if (::isInsideAMacroFx(m_parentFx.getPointer(), xsh) ||			  // link.
		::isInsideAMacroFx(m_oldFx.getPointer(), xsh) ||
		::isInsideAMacroFx(m_newFx.getPointer(), xsh))
		m_parentFx = TFxP();
}

//------------------------------------------------------

void SetParentUndo::redo() const
{
	/*
    Due to compatibility issues from *schematicnode.cpp files, the "do" operation cannot
    signal changes to the scene/xsheet... but the *re*do must.
  */

	redo_();
	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void SetParentUndo::redo_() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	TFx *parentFx = ::getActualIn(m_parentFx.getPointer());
	FxCommandUndo::attach(xsh, m_newFx.getPointer(), parentFx, m_parentPort, false);

	if (m_removeFromXsheet)
		xsh->getFxDag()->removeFromXsheet(m_newFx.getPointer());
}

//------------------------------------------------------

void SetParentUndo::undo() const
{
	TXsheet *xsh = m_xshHandle->getXsheet();

	TFx *parentFx = ::getActualIn(m_parentFx.getPointer());
	FxCommandUndo::attach(xsh, m_oldFx.getPointer(), parentFx, m_parentPort, false);

	if (m_removeFromXsheet)
		xsh->getFxDag()->addToXsheet(m_newFx.getPointer());

	m_xshHandle->notifyXsheetChanged();
}

//======================================================

void TFxCommand::setParent(TFx *fx, TFx *parentFx, int parentFxPort, TXsheetHandle *xshHandle)
{
	if (dynamic_cast<TXsheetFx *>(parentFx) || parentFxPort < 0) {
		std::auto_ptr<ConnectNodesToXsheetUndo> undo(new ConnectNodesToXsheetUndo(std::list<TFxP>(1, fx), xshHandle));
		if (undo->isConsistent()) {
			undo->redo_();
			TUndoManager::manager()->add(undo.release());
		}
	} else {
		std::auto_ptr<SetParentUndo> undo(new SetParentUndo(fx, parentFx, parentFxPort, xshHandle));
		if (undo->isConsistent()) {
			undo->redo_();
			TUndoManager::manager()->add(undo.release());
		}
	}
}

//**********************************************************************
//    Rename Fx  command
//**********************************************************************

class UndoRenameFx : public FxCommandUndo
{
	TFxP m_fx;
	std::wstring m_newName, m_oldName;

	TXsheetHandle *m_xshHandle;

public:
	UndoRenameFx(TFx *fx, const std::wstring &newName, TXsheetHandle *xshHandle)
		: m_fx(fx), m_newName(newName), m_oldName(::getActualIn(fx)->getName()), m_xshHandle(xshHandle) { assert(fx); }

	bool isConsistent() const { return true; }

	void redo() const
	{
		redo_();
		m_xshHandle->notifyXsheetChanged();
	}

	void redo_() const
	{
		::getActualIn(m_fx.getPointer())->setName(m_newName);
	}

	void undo() const
	{
		::getActualIn(m_fx.getPointer())->setName(m_oldName);
		m_xshHandle->notifyXsheetChanged();
	}

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Rename Fx : %1 > %2")
			.arg(QString::fromStdWString(m_oldName))
			.arg(QString::fromStdWString(m_newName));
	}
};

//======================================================

void TFxCommand::renameFx(TFx *fx, const std::wstring &newName, TXsheetHandle *xshHandle)
{
	if (!fx)
		return;

	std::auto_ptr<UndoRenameFx> undo(new UndoRenameFx(fx, newName, xshHandle));
	if (undo->isConsistent()) {
		undo->redo_();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Group Fxs  command
//**********************************************************************

class UndoGroupFxs : public FxCommandUndo
{
public:
	struct GroupData {
		TFxP m_fx;
		mutable int m_groupIndex; //! AKA group \a position (not \a id).

		GroupData(const TFxP &fx, int groupIdx = -1)
			: m_fx(fx), m_groupIndex(groupIdx) {}
	};

protected:
	std::vector<GroupData> m_groupData;
	int m_groupId;

	TXsheetHandle *m_xshHandle;

public:
	UndoGroupFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
		: m_groupData(fxs.begin(), fxs.end()), m_xshHandle(xshHandle)
	{
		initialize();
	}

	bool isConsistent() const { return !m_groupData.empty(); }

	void redo() const;
	void undo() const;

	int getSize() const { return sizeof(*this); }

	virtual QString getHistoryString()
	{
		return QObject::tr("Group Fx");
	}

protected:
	UndoGroupFxs(int groupId, TXsheetHandle *xshHandle)
		: m_groupId(groupId), m_xshHandle(xshHandle) {}

private:
	void initialize();
};

//------------------------------------------------------

void UndoGroupFxs::initialize()
{
	struct locals {
		inline static bool isXsheetFx(const GroupData &gd)
		{
			return dynamic_cast<TXsheet *>(gd.m_fx.getPointer());
		}
	};

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// Build a group id for the new group
	m_groupId = fxDag->getNewGroupId();

	// The xsheet fx must never be grouped
	m_groupData.erase(
		std::remove_if(m_groupData.begin(), m_groupData.end(), &locals::isXsheetFx),
		m_groupData.end());

	// Scan for macro fxs. A macro's internal fxs must be added to the group data, too
	// Yep, this is one of the few fx commands that do not require macro explosion.
	size_t g, gCount = m_groupData.size();
	for (g = 0; g != gCount; ++g) {
		if (TMacroFx *macro = dynamic_cast<TMacroFx *>(m_groupData[g].m_fx.getPointer())) {
			const std::vector<TFxP> &internalFxs = macro->getFxs();

			std::vector<TFxP>::const_iterator ft, fEnd = internalFxs.end();
			for (ft = internalFxs.begin(); ft != fEnd; ++ft)
				m_groupData.push_back(*ft);
		}
	}
}

//------------------------------------------------------

void UndoGroupFxs::redo() const
{
	const std::wstring groupName = L"Group " + std::to_wstring(m_groupId);

	std::vector<GroupData>::const_iterator gt, gEnd = m_groupData.end();
	for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
		// Insert the group id in the fx
		gt->m_groupIndex = gt->m_fx->getAttributes()->setGroupId(m_groupId);
		gt->m_fx->getAttributes()->setGroupName(groupName);
	}

	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void UndoGroupFxs::undo() const
{
	std::vector<GroupData>::const_iterator gt, gEnd = m_groupData.end();
	for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
		TCG_ASSERT(gt->m_groupIndex >= 0, continue);

		// Insert the group id in the fx
		gt->m_fx->getAttributes()->removeGroupId(gt->m_groupIndex);
		gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);

		gt->m_groupIndex = -1;
	}

	m_xshHandle->notifyXsheetChanged();
}

//======================================================

void TFxCommand::groupFxs(const std::list<TFxP> &fxs, TXsheetHandle *xshHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoGroupFxs(fxs, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Ungroup Fxs  command
//**********************************************************************

class UndoUngroupFxs : public UndoGroupFxs
{
public:
	UndoUngroupFxs(int groupId, TXsheetHandle *xshHandle)
		: UndoGroupFxs(groupId, xshHandle)
	{
		initialize();
	}

	void redo() const { UndoGroupFxs::undo(); }
	void undo() const { UndoGroupFxs::redo(); }

	QString getHistoryString()
	{
		return QObject::tr("Ungroup Fx");
	}

private:
	void initialize();
};

//------------------------------------------------------

void UndoUngroupFxs::initialize()
{
	struct {
		UndoUngroupFxs *m_this;

		void scanFxForGroup(TFx *fx)
		{
			if (fx) {
				const QStack<int> &groupStack = fx->getAttributes()->getGroupIdStack();

				int groupIdx = groupStack.indexOf(m_this->m_groupId); // Returns -1 if not found
				if (groupIdx >= 0)
					m_this->m_groupData.push_back(GroupData(fx, groupIdx));
			}
		}

	} locals = {this};

	TXsheet *xsh = m_xshHandle->getXsheet();
	FxDag *fxDag = xsh->getFxDag();

	// We must iterate the xsheet's fxs pool and look for fxs with the specified
	// group id

	// Search column fxs
	int c, cCount = xsh->getColumnCount();
	for (c = 0; c != cCount; ++c) {
		TXshColumn *column = xsh->getColumn(c);
		assert(column);

		locals.scanFxForGroup(column->getFx());
	}

	// Search normal fxs (not column ones)
	TFxSet *internalFxs = fxDag->getInternalFxs();

	int f, fCount = internalFxs->getFxCount();
	for (f = 0; f != fCount; ++f) {
		TFx *fx = internalFxs->getFx(f);
		locals.scanFxForGroup(fx);

		if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(fx)) {
			// Search internal macro fxs
			const std::vector<TFxP> &fxs = macroFx->getFxs();

			std::vector<TFxP>::const_iterator ft, fEnd = fxs.end();
			for (ft = fxs.begin(); ft != fEnd; ++ft)
				locals.scanFxForGroup(ft->getPointer());
		}
	}

	// Search output fxs
	int o, oCount = fxDag->getOutputFxCount();
	for (o = 0; o != oCount; ++o)
		locals.scanFxForGroup(fxDag->getOutputFx(o));
}

//======================================================

void TFxCommand::ungroupFxs(int groupId, TXsheetHandle *xshHandle)
{
	std::auto_ptr<FxCommandUndo> undo(new UndoUngroupFxs(groupId, xshHandle));
	if (undo->isConsistent()) {
		undo->redo();
		TUndoManager::manager()->add(undo.release());
	}
}

//**********************************************************************
//    Rename Group  command
//**********************************************************************

class UndoRenameGroup : public FxCommandUndo
{
	std::vector<UndoGroupFxs::GroupData> m_groupData;
	std::wstring m_oldGroupName, m_newGroupName;

	TXsheetHandle *m_xshHandle;

public:
	UndoRenameGroup(const std::list<TFxP> &fxs, const std::wstring &newGroupName, bool fromEditor, TXsheetHandle *xshHandle)
		: m_groupData(fxs.begin(), fxs.end()), m_newGroupName(newGroupName), m_xshHandle(xshHandle)
	{
		initialize(fromEditor);
	}

	bool isConsistent() const { return !m_groupData.empty(); }

	void redo() const;
	void undo() const;

	void redo_() const;

	int getSize() const { return sizeof(*this); }

	QString getHistoryString()
	{
		return QObject::tr("Rename Group  : %1 > %2")
			.arg(QString::fromStdWString(m_oldGroupName))
			.arg(QString::fromStdWString(m_newGroupName));
	}

private:
	void initialize(bool fromEditor);
};

//------------------------------------------------------

void UndoRenameGroup::initialize(bool fromEditor)
{
	struct locals {
		inline static bool isInvalid(const UndoGroupFxs::GroupData &gd) { return (gd.m_groupIndex < 0); }
	};

	if (!m_groupData.empty()) {
		m_oldGroupName = m_groupData.front().m_fx->getAttributes()->getGroupName(fromEditor);

		// Extract group indices
		std::vector<UndoGroupFxs::GroupData>::const_iterator gt, gEnd = m_groupData.end();
		for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
			const QStack<std::wstring> &groupNamesStack =
				gt->m_fx->getAttributes()->getGroupNameStack();

			gt->m_groupIndex = groupNamesStack.indexOf(m_oldGroupName); // Returns -1 if not found
			assert(gt->m_groupIndex >= 0);
		}
	}

	m_groupData.erase(std::remove_if(m_groupData.begin(), m_groupData.end(), &locals::isInvalid),
					  m_groupData.end());
}

//------------------------------------------------------

void UndoRenameGroup::redo() const
{
	redo_();
	m_xshHandle->notifyXsheetChanged();
}

//------------------------------------------------------

void UndoRenameGroup::redo_() const
{
	std::vector<UndoGroupFxs::GroupData>::const_iterator gt, gEnd = m_groupData.end();
	for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
		gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);
		gt->m_fx->getAttributes()->setGroupName(m_newGroupName, gt->m_groupIndex);
	}
}

//------------------------------------------------------

void UndoRenameGroup::undo() const
{
	std::vector<UndoGroupFxs::GroupData>::const_iterator gt, gEnd = m_groupData.end();
	for (gt = m_groupData.begin(); gt != gEnd; ++gt) {
		gt->m_fx->getAttributes()->removeGroupName(gt->m_groupIndex);
		gt->m_fx->getAttributes()->setGroupName(m_oldGroupName, gt->m_groupIndex);
	}

	m_xshHandle->notifyXsheetChanged();
}

//======================================================

void TFxCommand::renameGroup(const std::list<TFxP> &fxs, const std::wstring &name,
							 bool fromEditor, TXsheetHandle *xshHandle)
{
	std::auto_ptr<UndoRenameGroup> undo(new UndoRenameGroup(fxs, name, fromEditor, xshHandle));
	if (undo->isConsistent()) {
		undo->redo_(); // Same schematic nodes problem as above...   :(
		TUndoManager::manager()->add(undo.release());
	}
}