

#include "toonzqt/fxschematicscene.h"

// TnzQt includes
#include "toonzqt/fxtypes.h"
#include "toonzqt/fxschematicnode.h"
#include "toonzqt/gutil.h"
#include "toonzqt/dvdialog.h"
#include "toonzqt/fxselection.h"
#include "toonzqt/schematicgroupeditor.h"
#include "toonzqt/swatchviewer.h"
#include "toonzqt/tselectionhandle.h"

// TnzLib includes
#include "toonz/txsheet.h"
#include "toonz/toonzscene.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/fxdag.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/tcolumnfx.h"
#include "toonz/txshpalettecolumn.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/fxcommand.h"
#include "toonz/txsheethandle.h"
#include "toonz/tfxhandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/childstack.h"

// TnzBase includes
#include "tmacrofx.h"
#include "fxdata.h"
#include "tpassivecachemanager.h"
#include "tfxattributes.h"

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

// Qt includes
#include <QMenu>
#include <QApplication>
#include <QGraphicsSceneContextMenuEvent>
#include <QStack>

TEnv::IntVar IconifyFxSchematicNodes("IconifyFxSchematicNodes", 0);

namespace {

class MatchesFx {
  TFxP m_fx;

public:
  MatchesFx(const TFxP &fx) : m_fx(fx) {}

  bool operator()(const TFxP &fx) {
    TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx.getPointer());
    return (m_fx.getPointer() == fx.getPointer()) ||
           (zfx && m_fx.getPointer() == zfx->getZeraryFx());
  }
};

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

void keepSubgroup(QMap<int, QList<SchematicNode *>> &editedGroup) {
  QMap<int, QList<SchematicNode *>>::iterator it;
  for (it = editedGroup.begin(); it != editedGroup.end(); it++) {
    QList<SchematicNode *> groupedNodes = it.value();
    int i;
    for (i = 0; i < groupedNodes.size(); i++) {
      FxSchematicNode *node = dynamic_cast<FxSchematicNode *>(groupedNodes[i]);
      if (!node) continue;
      TFx *fx = node->getFx();
      assert(fx);
      QMap<int, QList<SchematicNode *>>::iterator it2;
      for (it2 = editedGroup.begin(); it2 != editedGroup.end(); it2++) {
        if (fx->getAttributes()->isContainedInGroup(it2.key()) &&
            !editedGroup[it2.key()].contains(node))
          editedGroup[it2.key()].append(node);
      }
    }
  }
}

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

//! Find the input and the output fx contained in \b visitedFxs.
//! \b visitedFxs must be a connected fx selection. In \b outputFx is put the
//! root of the connected fx selection.
//! In \b inputFx is put a leaf of the connected fx selection.
void findBoundariesFxs(TFx *&inputFx, TFx *&outputFx,
                       QMap<TFx *, bool> &visitedFxs, TFx *currentFx = 0) {
  if (visitedFxs.isEmpty()) return;
  if (!currentFx) currentFx = visitedFxs.begin().key();
  int inputPortCount        = currentFx->getInputPortCount();
  int outputConnectionCount = currentFx->getOutputConnectionCount();

  if (inputPortCount > 0 && !visitedFxs[currentFx]) {
    visitedFxs[currentFx] = true;
    TFxPort *fxPort       = currentFx->getInputPort(0);
    TFx *fx               = fxPort->getFx();
    if (fx && visitedFxs.count(fx) == 1) {
      if (!visitedFxs[fx]) findBoundariesFxs(inputFx, outputFx, visitedFxs, fx);
    } else
      inputFx = currentFx;
  } else
    inputFx = currentFx;
  if (!outputFx) {
    if (outputConnectionCount > 0) {
      TFx *fx = currentFx->getOutputConnection(0)->getOwnerFx();
      if (fx && visitedFxs.count(fx) == 1) {
        if (!visitedFxs[fx])
          findBoundariesFxs(inputFx, outputFx, visitedFxs, fx);
      } else
        outputFx = currentFx;
    } else
      outputFx = currentFx;
  }
}

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

bool canDisconnectSelection(FxSelection *selection) {
  QList<TFxP> selectedFx = selection->getFxs();
  int i;
  for (i = 0; i < selectedFx.size(); i++)  // .....
  {
    TFx *fx = selectedFx[i].getPointer();

    TLevelColumnFx *lcFx  = dynamic_cast<TLevelColumnFx *>(fx);
    TPaletteColumnFx *pfx = dynamic_cast<TPaletteColumnFx *>(fx);
    TXsheetFx *xfx        = dynamic_cast<TXsheetFx *>(fx);
    TOutputFx *ofx        = dynamic_cast<TOutputFx *>(fx);
    TZeraryColumnFx *zfx  = dynamic_cast<TZeraryColumnFx *>(fx);

    return (!lcFx && !pfx && !xfx && !ofx &&
            (!zfx || zfx->getInputPortCount() > 0));  // !!!!!
  }
  return false;
}

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

QList<TFxP> getRoots(const QList<TFxP> &fxs, TFxSet *terminals) {
  QList<TFxP> roots;
  int i;
  for (i = 0; i < fxs.size(); i++) {
    TFx *fx = fxs[i].getPointer();
    int j;
    bool isRoot = true;
    for (j = 0; j < fx->getOutputConnectionCount(); j++) {
      TFx *outFx = fx->getOutputConnection(j)->getOwnerFx();
      if (outFx &&
          std::find_if(fxs.begin(), fxs.end(), MatchesFx(outFx)) != fxs.end() &&
          !terminals->containsFx(fx))
        isRoot = false;
    }
    if (isRoot) roots.append(fx);
  }
  return roots;
}

bool resizingNodes = false;
bool updatingScene = false;

bool nodePosDefined(const TFx *fx1, const TFx *fx2) {
  bool isPosDefined[2] = {
      fx1->getAttributes()->getDagNodePos() != TConst::nowhere,
      fx2->getAttributes()->getDagNodePos() != TConst::nowhere};

  if (isPosDefined[0] == isPosDefined[1])
    return fx1->getIdentifier() < fx2->getIdentifier();
  else
    return isPosDefined[0];
}

}  // namespace

//==================================================================
//
// FxSchematicScene::SupportLinks
//
//==================================================================

void FxSchematicScene::SupportLinks::addBridgeLink(SchematicLink *link) {
  if (link && !m_bridges.contains(link)) m_bridges.push_back(link);
}

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

void FxSchematicScene::SupportLinks::addOutputLink(SchematicLink *link) {
  if (link && !m_outputs.contains(link)) m_outputs.push_back(link);
}

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

void FxSchematicScene::SupportLinks::addInputLink(SchematicLink *link) {
  if (link && !m_inputs.contains(link)) m_inputs.push_back(link);
}

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

void FxSchematicScene::SupportLinks::hideBridgeLinks() {
  int i;
  for (i = 0; i < m_bridges.size(); i++) m_bridges[i]->hide();
}

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

void FxSchematicScene::SupportLinks::hideInputLinks() {
  int i;
  for (i = 0; i < m_inputs.size(); i++) m_inputs[i]->hide();
}

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

void FxSchematicScene::SupportLinks::hideOutputLinks() {
  int i;
  for (i = 0; i < m_outputs.size(); i++) m_outputs[i]->hide();
}

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

void FxSchematicScene::SupportLinks::showBridgeLinks() {
  int i;
  for (i = 0; i < m_bridges.size(); i++) m_bridges[i]->show();
}

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

void FxSchematicScene::SupportLinks::showInputLinks() {
  int i;
  for (i = 0; i < m_inputs.size(); i++) m_inputs[i]->show();
}

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

void FxSchematicScene::SupportLinks::showOutputLinks() {
  int i;
  for (i = 0; i < m_outputs.size(); i++) m_outputs[i]->show();
}

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

void FxSchematicScene::SupportLinks::removeBridgeLinks(bool deleteLink) {
  int i;
  for (i = 0; i < m_bridges.size(); i++) {
    SchematicLink *link = m_bridges[i];
    m_bridges.removeAt(i);
    if (deleteLink) {
      link->getStartPort()->removeLink(link);
      link->getEndPort()->removeLink(link);
      delete link;
    }
  }
}

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

void FxSchematicScene::SupportLinks::removeInputLinks(bool deleteLink) {
  int i;
  for (i = 0; i < m_inputs.size(); i++) {
    SchematicLink *link = m_inputs[i];
    m_inputs.removeAt(i);
    if (deleteLink) {
      link->getStartPort()->removeLink(link);
      link->getEndPort()->removeLink(link);
      delete link;
    }
  }
}

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

void FxSchematicScene::SupportLinks::removeOutputLinks(bool deleteLink) {
  int i;
  for (i = 0; i < m_outputs.size(); i++) {
    SchematicLink *link = m_outputs[i];
    m_outputs.removeAt(i);
    if (deleteLink) {
      link->getStartPort()->removeLink(link);
      link->getEndPort()->removeLink(link);
      delete link;
    }
  }
}

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

void FxSchematicScene::SupportLinks::clearAll() {
  m_bridges.clear();
  m_inputs.clear();
  m_outputs.clear();
}

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

int FxSchematicScene::SupportLinks::size() {
  return m_bridges.size() + m_inputs.size() + m_outputs.size();
}

//==================================================================
//
// FxSchematicScene
//
//==================================================================

FxSchematicScene::FxSchematicScene(QWidget *parent)
    : SchematicScene(parent)
    , m_firstPoint(sceneRect().center())
    , m_xshHandle(0)
    , m_fxHandle(0)
    , m_addFxContextMenu()
    , m_disconnectionLinks()
    , m_connectionLinks()
    , m_isConnected(false)
    , m_linkUnlinkSimulation(false)
    , m_altPressed(false)
    , m_lastPos(0, 0)
    , m_currentFxNode(0)
    , m_gridDimension(eSmall)
    , m_isNormalIconView(!IconifyFxSchematicNodes)
    , m_viewer() {
  m_viewer = (SchematicViewer *)parent;

  m_selection = new FxSelection();
  m_selection->setFxSchematicScene(this);

  connect(m_selection, SIGNAL(doCollapse(const QList<TFxP> &)), this,
          SLOT(onCollapse(const QList<TFxP> &)));
  connect(m_selection, SIGNAL(doExplodeChild(const QList<TFxP> &)), this,
          SIGNAL(doExplodeChild(const QList<TFxP> &)));
  connect(this, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged()));

  m_addFxContextMenu.setSelection(m_selection);
  m_highlightedLinks.clear();
}

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

FxSchematicScene::~FxSchematicScene() {
  if (m_selection) delete m_selection;
}

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

void FxSchematicScene::setApplication(TApplication *app) {
  m_app = app;

  m_xshHandle    = app->getCurrentXsheet();
  m_fxHandle     = app->getCurrentFx();
  m_frameHandle  = app->getCurrentFrame();
  m_columnHandle = app->getCurrentColumn();

  if (m_fxHandle)
    connect(m_fxHandle, SIGNAL(fxSwitched()), this,
            SLOT(onCurrentFxSwitched()));

  m_addFxContextMenu.setApplication(app);

  m_selection->setXsheetHandle(m_xshHandle);
  m_selection->setFxHandle(m_fxHandle);
}

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

void FxSchematicScene::updateScene() {
  if (updatingScene) return;
  updatingScene = true;
  if (!views().empty()) m_disconnectionLinks.clearAll();
  m_connectionLinks.clearAll();
  m_selectionOldPos.clear();

  clearSelection();
  clearAllItems();

  m_table.clear();
  m_groupedTable.clear();
  m_groupEditorTable.clear();
  m_macroEditorTable.clear();

  m_currentFxNode = 0;

  // GroupId->Fx
  QMap<int, QList<TFxP>> groupedFxs;
  QMap<int, QList<SchematicNode *>> editedGroup;
  QMap<TMacroFx *, QList<SchematicNode *>> editedMacro;

  TXsheet *xsh    = m_xshHandle->getXsheet();
  m_gridDimension = xsh->getFxDag()->getDagGridDimension();
  TFxSet *fxSet   = xsh->getFxDag()->getInternalFxs();
  int i;

  FxDag *fxDag = xsh->getFxDag();
  // Add XSheetFX node
  addFxSchematicNode(fxDag->getXsheetFx());

  // Add outputFx nodes
  int k;
  for (k = 0; k < fxDag->getOutputFxCount(); k++) {
    TOutputFx *fx = fxDag->getOutputFx(k);
    if (fx->getAttributes()->isGrouped() &&
        !fx->getAttributes()->isGroupEditing()) {
      groupedFxs[fx->getAttributes()->getGroupId()].push_back(fx);
      continue;
    }
    SchematicNode *node = addFxSchematicNode(fx);
    if (fx->getAttributes()->isGrouped())
      editedGroup[fx->getAttributes()->getEditingGroupId()].append(node);
  }

  // Add columnFx and zeraryFx nodes
  for (i = 0; i < xsh->getColumnCount(); i++) {
    TXshColumn *column = xsh->getColumn(i);
    TFx *fx            = 0;
    if (TXshLevelColumn *lc = column->getLevelColumn())
      fx = lc->getLevelColumnFx();
    else if (TXshPaletteColumn *pc = dynamic_cast<TXshPaletteColumn *>(column))
      fx = pc->getPaletteColumnFx();
    else if (TXshZeraryFxColumn *zc =
                 dynamic_cast<TXshZeraryFxColumn *>(column))
      fx = zc->getZeraryColumnFx();
    if (!fx) continue;

    if (fx->getAttributes()->isGrouped() &&
        !fx->getAttributes()->isGroupEditing()) {
      groupedFxs[fx->getAttributes()->getGroupId()].push_back(fx);
      continue;
    }
    if (column->isEmpty() && fx && fx->getOutputConnectionCount() == 0)
      continue;
    SchematicNode *node = addFxSchematicNode(fx);
    if (fx->getAttributes()->isGrouped())
      editedGroup[fx->getAttributes()->getEditingGroupId()].append(node);
  }

  // Add normalFx
  QList<TFx *> fxsToBePlaced;
  for (i = 0; i < fxSet->getFxCount(); i++) {
    TFx *fx         = fxSet->getFx(i);
    TMacroFx *macro = dynamic_cast<TMacroFx *>(fx);
    if (fx->getAttributes()->isGrouped() &&
        !fx->getAttributes()->isGroupEditing()) {
      groupedFxs[fx->getAttributes()->getGroupId()].push_back(fx);
      continue;
    } else if (macro && macro->isEditing()) {
      std::vector<TFxP> fxs = macro->getFxs();
      int j;
      for (j = 0; j < (int)fxs.size(); j++) {
        SchematicNode *node = addFxSchematicNode(fxs[j].getPointer());
        editedMacro[macro].append(node);
        if (fxs[j]->getAttributes()->isGrouped() &&
            macro->getAttributes()->isGroupEditing())
          editedGroup[fx->getAttributes()->getEditingGroupId()].append(node);
      }
      continue;
    }
    fxsToBePlaced.append(fx);
  }

  // sorting fxs so that fxs with specified positions are placed first
  std::sort(fxsToBePlaced.begin(), fxsToBePlaced.end(), nodePosDefined);

  for (auto fx : fxsToBePlaced) {
    SchematicNode *node = addFxSchematicNode(fx);
    if (fx->getAttributes()->isGrouped())
      editedGroup[fx->getAttributes()->getEditingGroupId()].append(node);
    TMacroFx *macro = dynamic_cast<TMacroFx *>(fx);
    // If adding an unedited macro and nodes are not yet set, let's position the
    // internal nodes now
    if (macro) {
      double minY           = macro->getAttributes()->getDagNodePos().y;
      double maxX           = macro->getAttributes()->getDagNodePos().x;
      double y              = minY;
      double x              = maxX;
      std::vector<TFxP> fxs = macro->getFxs();
      for (int j = 0; j < (int)fxs.size(); j++) {
        TFx *macroFx = fxs[j].getPointer();
        if (macroFx && !m_placedFxs.contains(macroFx)) {
          placeNodeAndParents(macroFx, x, maxX, minY);
          y -= (m_gridDimension == eLarge ? 100 : 50);
          minY = std::min(y, minY);
        }
      }
    }
  }

  // grouped node
  QMap<int, QList<TFxP>>::const_iterator it;
  for (it = groupedFxs.begin(); it != groupedFxs.end(); it++) {
    FxSchematicNode *node = addGroupedFxSchematicNode(it.key(), it.value());
    TFx *fx               = node->getFx();
    assert(fx);
    int editingGroupId = fx->getAttributes()->getEditingGroupId();
    if (editingGroupId != -1) editedGroup[editingGroupId].append(node);
  }

  keepSubgroup(editedGroup);
  updateEditedGroups(editedGroup);
  updateEditedMacros(editedMacro);
  updateLink();
  m_nodesToPlace.clear();
  updatingScene = false;
}

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

void FxSchematicScene::updateEditedGroups(
    const QMap<int, QList<SchematicNode *>> &editedGroup) {
  QMap<int, QList<SchematicNode *>>::const_iterator it;
  for (it = editedGroup.begin(); it != editedGroup.end(); it++) {
    int zValue                                            = 2;
    QMap<int, QList<SchematicNode *>>::const_iterator it2 = editedGroup.begin();
    while (it2 != editedGroup.end()) {
      FxSchematicNode *placedFxNode =
          dynamic_cast<FxSchematicNode *>(it2.value()[0]);
      FxSchematicNode *fxNode = dynamic_cast<FxSchematicNode *>(it.value()[0]);
      if (!placedFxNode || !fxNode) {
        it2++;
        continue;
      }
      int placedGroupedId =
          placedFxNode->getFx()->getAttributes()->getEditingGroupId();
      if (fxNode->getFx()->getAttributes()->isContainedInGroup(
              placedGroupedId) &&
          fxNode->getFx()->getAttributes()->getEditingGroupId() != it2.key())
        zValue += 2;
      it2++;
    }
    FxSchematicGroupEditor *node =
        addEditedGroupedFxSchematicNode(it.key(), it.value());
    node->setZValue(zValue);
    node->setGroupedNodeZValue(zValue + 1);
  }
}

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

void FxSchematicScene::updateEditedMacros(
    const QMap<TMacroFx *, QList<SchematicNode *>> &editedMacro) {
  QMap<TMacroFx *, QList<SchematicNode *>>::const_iterator it;
  for (it = editedMacro.begin(); it != editedMacro.end(); it++) {
    TMacroFx *macro = it.key();
    int zValue      = 2;
    if (macro->getAttributes()->isGrouped()) {
      FxSchematicGroupEditor *containingGroup =
          m_groupEditorTable[macro->getAttributes()->getEditingGroupId()];
      assert(containingGroup);
      zValue = containingGroup->zValue() + 2;
    }
    FxSchematicMacroEditor *node =
        addEditedMacroFxSchematicNode(it.key(), it.value());
    node->setZValue(zValue);
    node->setGroupedNodeZValue(zValue + 1);
  }
}

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

FxSchematicNode *FxSchematicScene::addFxSchematicNode(TFx *fx) {
  FxSchematicNode *node = createFxSchematicNode(fx);
  if (!node) return 0;
  connect(node, SIGNAL(sceneChanged()), this, SLOT(onSceneChanged()));
  connect(node, SIGNAL(xsheetChanged()), this, SLOT(onXsheetChanged()));
  connect(node, SIGNAL(switchCurrentFx(TFx *)), this,
          SLOT(onSwitchCurrentFx(TFx *)));
  connect(node, SIGNAL(currentColumnChanged(int)), this,
          SLOT(onCurrentColumnChanged(int)));

  connect(node, SIGNAL(fxNodeDoubleClicked()), this,
          SLOT(onFxNodeDoubleClicked()));

  connect(node, SIGNAL(nodeChangedSize()), this, SLOT(onNodeChangedSize()));

  if (fx->getAttributes()->getDagNodePos() == TConst::nowhere) {
    node->resize(m_gridDimension == 0);
    placeNode(node);
  } else
    updatePosition(node, fx->getAttributes()->getDagNodePos());
  m_table[fx] = node;
  return node;
}

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

FxSchematicNode *FxSchematicScene::addGroupedFxSchematicNode(
    int groupId, const QList<TFxP> &groupedFxs) {
  TFxSet *terminals = getXsheet()->getFxDag()->getTerminalFxs();
  QList<TFxP> roots = getRoots(groupedFxs, terminals);
  if (roots.isEmpty()) return 0;
  std::wstring name = roots[0]->getAttributes()->getGroupName(false);
  FxGroupNode *node = new FxGroupNode(this, groupedFxs, roots, groupId, name);
  if (!node) return 0;
  connect(node, SIGNAL(sceneChanged()), this, SLOT(onSceneChanged()));
  connect(node, SIGNAL(xsheetChanged()), this, SLOT(onXsheetChanged()));
  connect(node, SIGNAL(switchCurrentFx(TFx *)), this,
          SLOT(onSwitchCurrentFx(TFx *)));
  connect(node, SIGNAL(currentColumnChanged(int)), this,
          SLOT(onCurrentColumnChanged(int)));
  connect(node, SIGNAL(fxNodeDoubleClicked()), this,
          SLOT(onFxNodeDoubleClicked()));
  m_groupedTable[groupId] = node;
  return node;
}

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

FxSchematicGroupEditor *FxSchematicScene::addEditedGroupedFxSchematicNode(
    int groupId, const QList<SchematicNode *> &groupedFxs) {
  FxSchematicGroupEditor *editorGroup =
      new FxSchematicGroupEditor(groupId, groupedFxs, this);
  m_groupEditorTable[groupId] = editorGroup;
  return editorGroup;
}

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

FxSchematicMacroEditor *FxSchematicScene::addEditedMacroFxSchematicNode(
    TMacroFx *macro, const QList<SchematicNode *> &groupedFxs) {
  FxSchematicMacroEditor *editorMacro =
      new FxSchematicMacroEditor(macro, groupedFxs, this);
  m_macroEditorTable[macro] = editorMacro;
  return editorMacro;
}

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

void FxSchematicScene::updatePosition(FxSchematicNode *node,
                                      const TPointD &pos) {
  node->setPos(QPointF(pos.x, pos.y));
  node->getFx()->getAttributes()->setDagNodePos(pos);
  QVector<SchematicNode *> placedNodes = getPlacedNode(node);
  int step                             = m_gridDimension == eLarge ? 100 : 50;
  TPointD offset(0, -step);
  int i;
  for (i = 0; i < placedNodes.size(); i++) {
    FxSchematicNode *placedNode =
        dynamic_cast<FxSchematicNode *>(placedNodes[i]);
    assert(placedNode);
    TPointD newPos =
        placedNode->getFx()->getAttributes()->getDagNodePos() + offset;
    updatePosition(placedNode, newPos);
  }
}

//------------------------------------------------------------------
/*! create node depends on the fx type
 */
FxSchematicNode *FxSchematicScene::createFxSchematicNode(TFx *fx) {
  if (TLevelColumnFx *lcFx = dynamic_cast<TLevelColumnFx *>(fx))
    return new FxSchematicColumnNode(this, lcFx);
  else if (TPaletteColumnFx *pfx = dynamic_cast<TPaletteColumnFx *>(fx))
    return new FxSchematicPaletteNode(this, pfx);
  else if (TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx))
    return new FxSchematicZeraryNode(this, zfx);
  else if (TXsheetFx *xfx = dynamic_cast<TXsheetFx *>(fx))
    return new FxSchematicXSheetNode(this, xfx);
  else if (TOutputFx *ofx = dynamic_cast<TOutputFx *>(fx))
    return new FxSchematicOutputNode(this, ofx);
  else if (fx && fx->getFxType().find("nothingFx") !=
                     std::string::npos)  // pass-through node
    return new FxSchematicPassThroughNode(this, fx);
  else
    return new FxSchematicNormalFxNode(this, fx);
}

//------------------------------------------------------------------
/*! place nodes of which positions are not specified manually
 */
void FxSchematicScene::placeNode(FxSchematicNode *node) {
  if (!node) return;
  int step        = m_gridDimension == eLarge ? 100 : 50;
  FxDag *fxDag    = m_xshHandle->getXsheet()->getFxDag();
  QRectF nodeRect = node->boundingRect();
  if (node->isA(eOutpuFx)) {
    // I'm placing an output node
    TFx *xsheetFx        = fxDag->getXsheetFx();
    TFxPort *outPort     = xsheetFx->getOutputConnection(0);
    TFx *connectedOutput = outPort ? outPort->getOwnerFx() : 0;
    if (connectedOutput && connectedOutput == node->getFx()) {
      // The output node is connected to the xsheet node
      TPointD pos = xsheetFx->getAttributes()->getDagNodePos();
      if (pos != TConst::nowhere)
        nodeRect.translate(pos.x + 120, pos.y);
      else
        nodeRect.translate(sceneRect().center());
      while (!isAnEmptyZone(nodeRect)) nodeRect.translate(0, step);
    } else {
      // The output node is not connected to the xsheet node
      TFx *fx       = node->getFx();
      TFxPort *port = fx->getInputPort(0);
      TFx *inputFx  = port->getFx();
      if (inputFx) {
        // The output node is connected to another node
        TPointD pos = inputFx->getAttributes()->getDagNodePos();
        if (pos != TConst::nowhere)
          nodeRect.translate(pos.x + 120, pos.y);
        else {
          m_nodesToPlace[inputFx].append(node);
          return;
        }
      } else {
        // The output node is not connected
        QPointF pos = sceneRect().center();
        nodeRect.translate(pos);
      }
    }
    while (!isAnEmptyZone(nodeRect)) nodeRect.translate(0, step);
    QPointF newPos = nodeRect.topLeft();
    node->getFx()->getAttributes()->setDagNodePos(
        TPointD(newPos.x(), newPos.y()));
    node->setPos(newPos);
    return;
  } else if (node->isA(eXSheetFx)) {
    // I'm placing the xsheet node
    TFxSet *terminalFxs = fxDag->getTerminalFxs();
    int i;
    double maxX = m_firstPoint.x();
    for (i = 0; i < terminalFxs->getFxCount(); i++) {
      TFx *terminalFx = terminalFxs->getFx(i);
      if (terminalFx->getAttributes()->getDagNodePos() == TConst::nowhere)
        continue;
      maxX = std::max(maxX, terminalFx->getAttributes()->getDagNodePos().x);
    }
    TPointD oldPos = node->getFx()->getAttributes()->getDagNodePos();
    QPointF pos;
    if (oldPos == TConst::nowhere)
      pos = QPointF(maxX + 120, m_firstPoint.y());
    else
      pos = QPointF(maxX + 120 > oldPos.x ? maxX + 120 : oldPos.x, oldPos.y);
    node->getFx()->getAttributes()->setDagNodePos(TPointD(pos.x(), pos.y()));
    node->setPos(pos);
    return;
  } else if (node->isA(eMacroFx)) {
    double minX = TConst::nowhere.x, minY = TConst::nowhere.y, maxY;
    QPointF pos;
    TMacroFx *macroFx     = dynamic_cast<TMacroFx *>(node->getFx());
    std::vector<TFxP> fxs = macroFx->getFxs();
    int k;
    for (k = 0; k < (int)fxs.size(); k++) {
      TFx *fx = fxs[k].getPointer();
      if (fx->getAttributes()->getDagNodePos() == TConst::nowhere) continue;
      if (QPointF(minX, minY) ==
          QPointF(TConst::nowhere.x, TConst::nowhere.y)) {
        minX = fx->getAttributes()->getDagNodePos().x;
        minY = maxY = fx->getAttributes()->getDagNodePos().y;
        continue;
      }
      minX = std::min(fx->getAttributes()->getDagNodePos().x, minX);
      minY = std::min(fx->getAttributes()->getDagNodePos().y, minY);
      maxY = std::max(fx->getAttributes()->getDagNodePos().y, maxY);
    }
    if (QPointF(minX, minY) == QPointF(TConst::nowhere.x, TConst::nowhere.y)) {
      TFx *inputFx = node->getFx()->getInputPort(0)->getFx();
      if (inputFx &&
          inputFx->getAttributes()->getDagNodePos() != TConst::nowhere) {
        TPointD dagPos =
            inputFx->getAttributes()->getDagNodePos() + TPointD(150, 0);
        pos = QPointF(dagPos.x, dagPos.y);
      } else
        pos = sceneRect().center();
      nodeRect.moveTopLeft(pos);
      while (!isAnEmptyZone(nodeRect)) nodeRect.translate(0, -step);
      pos = nodeRect.topLeft();
    } else {
      pos.setX(minX);
      pos.setY((maxY + minY) / 2);
    }
    node->getFx()->getAttributes()->setDagNodePos(TPointD(pos.x(), pos.y()));
    node->setPos(QPointF(pos));
    if (m_nodesToPlace.contains(node->getFx())) {
      QList<FxSchematicNode *> nodes = m_nodesToPlace[node->getFx()];
      int i;
      for (i = 0; i < nodes.size(); i++) placeNode(nodes[i]);
    }
    return;
  } else if (node->isA(eNormalFx) || node->isA(eNormalLayerBlendingFx) ||
             node->isA(eNormalMatteFx) || node->isA(eNormalImageAdjustFx)) {
    // I'm placing an effect or a macro
    TFx *inputFx = node->getFx()->getInputPort(0)->getFx();
    QPointF pos;
    if (inputFx) {
      if (inputFx->getAttributes()->getDagNodePos() != TConst::nowhere) {
        TPointD dagPos =
            inputFx->getAttributes()->getDagNodePos() + TPointD(150, 0);
        pos = QPointF(dagPos.x, dagPos.y);
        nodeRect.moveTopLeft(pos);

        while (!isAnEmptyZone_withParentFx(nodeRect, inputFx))
          nodeRect.translate(0, -step);
        pos = nodeRect.topLeft();
      } else {
        m_nodesToPlace[inputFx].append(node);
        return;
      }
    } else {
      pos = sceneRect().center();
      nodeRect.moveTopLeft(pos);
      while (!isAnEmptyZone(nodeRect)) nodeRect.translate(0, -step);
      pos = nodeRect.topLeft();
    }
    node->getFx()->getAttributes()->setDagNodePos(TPointD(pos.x(), pos.y()));
    node->setPos(QPointF(pos));
    if (m_nodesToPlace.contains(node->getFx())) {
      QList<FxSchematicNode *> nodes = m_nodesToPlace[node->getFx()];
      int i;
      for (i = 0; i < nodes.size(); i++) placeNode(nodes[i]);
    }
    return;
  } else if (node->isA(eZeraryFx) || node->isA(eColumnFx) ||
             node->isA(eGroupedFx)) {
    // I'm placing a column
    nodeRect.translate(m_firstPoint);
    nodeRect.translate(10, 10);
    while (!isAnEmptyZone(nodeRect)) nodeRect.translate(0, -step);
    QPointF newPos = nodeRect.topLeft();
    node->getFx()->getAttributes()->setDagNodePos(
        TPointD(newPos.x(), newPos.y()));
    node->setPos(QPointF(newPos));
    return;
  }
}

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

void FxSchematicScene::updateLink() {
  TXsheet *xsh = m_xshHandle->getXsheet();

  // Iterate the fxs table
  QMap<TFx *, FxSchematicNode *>::iterator it;
  for (it = m_table.begin(); it != m_table.end(); ++it) {
    FxSchematicNode *node = it.value();
    if (!node) continue;  // Should be asserted? Is it legal?

    TFx *fx           = it.key();
    TFx *inputPortsFx = fx;

    if (TZeraryColumnFx *fx2 = dynamic_cast<TZeraryColumnFx *>(fx)) {
      inputPortsFx = fx2->getZeraryFx();
      if (!inputPortsFx)
        return;  // Should really never happen. Should be asserted...
    }

    for (int i = 0; i != inputPortsFx->getInputPortCount(); ++i) {
      TFxPort *port = inputPortsFx->getInputPort(i);

      if (TFx *linkedFx = port->getFx()) {
        if (!linkedFx->getAttributes()->isGrouped() ||
            linkedFx->getAttributes()->isGroupEditing()) {
          // Not in a group / open group case
          assert(m_table.contains(linkedFx));

          if (m_table.contains(linkedFx)) {
            FxSchematicNode *linkedNode = m_table[linkedFx];
            SchematicPort *p0           = linkedNode->getOutputPort();
            SchematicPort *p1           = node->getInputPort(i);
            if (p0 && p1) p0->makeLink(p1);
          }
        } else {
          assert(
              m_groupedTable.contains(linkedFx->getAttributes()->getGroupId()));

          if (m_groupedTable.contains(
                  linkedFx->getAttributes()->getGroupId())) {
            FxSchematicNode *linkedNode =
                m_groupedTable[linkedFx->getAttributes()->getGroupId()];
            SchematicPort *p0 = linkedNode->getOutputPort();
            SchematicPort *p1 = node->getInputPort(i);
            if (p0 && p1) p0->makeLink(p1);
          }
        }
      }
    }
    if (xsh->getFxDag()->getTerminalFxs()->containsFx(fx)) {
      SchematicPort *p0 = node->getOutputPort();
      SchematicPort *p1 =
          m_table[xsh->getFxDag()->getXsheetFx()]->getInputPort(0);
      p0->makeLink(p1);
    }
  }
  QMap<int, FxGroupNode *>::iterator it2;
  for (it2 = m_groupedTable.begin(); it2 != m_groupedTable.end(); it2++) {
    FxGroupNode *node = it2.value();
    if (!node) continue;
    int i, fxCount = node->getFxCount();
    bool xsheetConnected = false;
    for (i = 0; i < fxCount; i++) {
      TFx *fx = node->getFx(i);
      if (xsh->getFxDag()->getTerminalFxs()->containsFx(fx) &&
          !xsheetConnected) {
        SchematicPort *p0 = node->getOutputPort();
        SchematicPort *p1 =
            m_table[xsh->getFxDag()->getXsheetFx()]->getInputPort(0);
        p0->makeLink(p1);
        xsheetConnected = true;
      }

      TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx);
      if (zfx) fx = zfx->getZeraryFx();
      if (fx) {
        int j;
        for (j = 0; j < fx->getInputPortCount(); j++) {
          TFx *linkedFx = fx->getInputPort(j)->getFx();
          if (!linkedFx) continue;
          if (!linkedFx->getAttributes()->isGrouped() ||
              linkedFx->getAttributes()->isGroupEditing()) {
            assert(m_table.contains(linkedFx));
            if (m_table.contains(linkedFx)) {
              FxSchematicNode *linkedNode = m_table[linkedFx];
              SchematicPort *p0           = linkedNode->getOutputPort();
              SchematicPort *p1           = node->getInputPort(0);
              if (p0 && p1) p0->makeLink(p1);
            }
          } else {
            int linkedGroupId = linkedFx->getAttributes()->getGroupId();
            assert(m_groupedTable.contains(linkedGroupId));
            if (m_groupedTable.contains(linkedGroupId)) {
              FxGroupNode *linkedNode = m_groupedTable[linkedGroupId];
              if (linkedNode == node) continue;
              SchematicPort *p0 = linkedNode->getOutputPort();
              SchematicPort *p1 = node->getInputPort(0);
              if (p0 && p1 && !p0->isLinkedTo(p1)) p0->makeLink(p1);
            }
          }
        }
      }
    }
  }

  // to solve an edit macro problem: create a dummy link
  QMap<TMacroFx *, FxSchematicMacroEditor *>::iterator it3;
  for (it3 = m_macroEditorTable.begin(); it3 != m_macroEditorTable.end();
       it3++) {
    TMacroFx *macro = it3.key();
    int i;
    FxSchematicNode *root = m_table[macro->getRoot()];
    SchematicPort *p0     = root->getOutputPort();
    for (i = 0; i < macro->getOutputConnectionCount(); i++) {
      TFxPort *outConnection = macro->getOutputConnection(i);
      TFx *outFx             = outConnection->getOwnerFx();
      TMacroFx *outMacroFx   = dynamic_cast<TMacroFx *>(outFx);
      if (outMacroFx && outMacroFx->isEditing()) {
        std::vector<TFxP> fxs = outMacroFx->getFxs();
        int k;
        for (k = 0; k < (int)fxs.size(); k++) {
          TFx *fx = fxs[k].getPointer();
          int j;
          for (j = 0; j < fx->getInputPortCount(); j++)
            if (outConnection == fx->getInputPort(j)) {
              outFx = fx;
              break;
            }
          if (outFx != outMacroFx) break;
        }
      }

      int j;
      for (j = 0; j < outFx->getInputPortCount(); j++)
        if (outFx->getInputPort(j)->getFx() == macro) {
          SchematicPort *p1 = m_table[outFx]->getInputPort(j);
          p0->makeLink(p1);
          break;
        }
    }
    if (xsh->getFxDag()->getTerminalFxs()->containsFx(macro)) {
      assert(root);
      if (!root) continue;
      SchematicPort *p1 =
          m_table[xsh->getFxDag()->getXsheetFx()]->getInputPort(0);
      p0->makeLink(p1);
    }
  }
  updateDuplcatedNodesLink();
}

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

void FxSchematicScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *cme) {
  QPointF scenePos                = cme->scenePos();
  QList<QGraphicsItem *> itemList = items(scenePos);
  if (!itemList.isEmpty()) {
    SchematicScene::contextMenuEvent(cme);
    return;
  }

  QMenu menu(views()[0]);

  if (cme->modifiers() & Qt::ControlModifier) {
    menu.addAction(m_addFxContextMenu.getAgainCommand(AddFxContextMenu::Add));
    if (!menu.actions().isEmpty()) {
      menu.exec(cme->screenPos());
      return;
    }
  }

  QAction *addOutputFx =
      CommandManager::instance()->getAction("MI_NewOutputFx");
  QAction *copy  = CommandManager::instance()->getAction("MI_Copy");
  QAction *cut   = CommandManager::instance()->getAction("MI_Cut");
  QAction *paste = CommandManager::instance()->getAction("MI_Paste");

  m_addFxContextMenu.setCurrentCursorScenePos(cme->scenePos());

  menu.addMenu(m_addFxContextMenu.getAddMenu());
  if (addOutputFx) menu.addAction(addOutputFx);

  // Close sub xsheet and move to parent sheet
  ToonzScene *scene      = getXsheet()->getScene();
  ChildStack *childStack = scene->getChildStack();
  if (childStack && childStack->getAncestorCount() > 0) {
    menu.addSeparator();
    menu.addAction(CommandManager::instance()->getAction("MI_CloseChild"));
  }

  menu.addSeparator();
  menu.addAction(copy);
  menu.addAction(cut);
  menu.addAction(paste);
  m_selection->setPastePosition(TPointD(scenePos.x(), scenePos.y()));
  menu.exec(cme->screenPos());
  m_selection->setPastePosition(TConst::nowhere);
}

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

QPointF FxSchematicScene::nearestPoint(const QPointF &point) {
  QRectF rect(0, 0, 0.1, 0.1);
  rect.moveCenter(point);
  QList<QGraphicsItem *> itemList = items(rect);
  while (itemList.isEmpty()) {
    rect.adjust(-0.1, -0.1, 0.1, 0.1);
    itemList = items(rect);
  }
  /*
  FIXME: QTransform() のデフォルトは Qt4.8 の itemAt() と比べて equivant
  だろうか？
*/
  QGraphicsItem *item = itemAt(rect.bottomLeft(), QTransform());
  if (item) return rect.bottomLeft();
  item = itemAt(rect.bottomRight(), QTransform());
  if (item) return rect.bottomRight();
  item = itemAt(rect.topLeft(), QTransform());
  if (item) return rect.topLeft();
  item = itemAt(rect.topRight(), QTransform());
  if (item) return rect.topRight();
  return QPointF();
}

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

FxSchematicNode *FxSchematicScene::getFxNodeFromPosition(const QPointF &pos) {
  QList<QGraphicsItem *> pickedItems = items(pos);
  for (int i = 0; i < pickedItems.size(); i++) {
    FxSchematicNode *fxNode =
        dynamic_cast<FxSchematicNode *>(pickedItems.at(i));
    if (fxNode) return fxNode;
    FxSchematicPort *fxPort =
        dynamic_cast<FxSchematicPort *>(pickedItems.at(i));
    if (fxPort) return fxPort->getDock()->getNode();
  }
  return 0;
}

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

void FxSchematicScene::updateDuplcatedNodesLink() {
  QMap<TFx *, FxSchematicNode *>::iterator it;

  // fx2node contains only duplicated nodes
  // and zerary duplicated node s
  QMap<TFx *, FxSchematicNode *> fx2node;
  for (it = m_table.begin(); it != m_table.end(); ++it) {
    TFx *fx               = it.key();
    FxSchematicNode *node = it.value();
    if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx)) {
      fx = zcfx->getZeraryFx();
      if (!fx) return;
    }
    assert(fx2node.count(fx) == 0);
    if (fx->getLinkedFx() == fx) continue;
    fx2node[fx] = node;
  }

  // trovo i link
  std::set<TFx *> visited;
  for (it = fx2node.begin(); it != fx2node.end(); ++it) {
    TFx *fx               = it.key();
    FxSchematicNode *node = it.value();
    assert(fx->getLinkedFx() != fx);
    if (visited.count(fx) > 0) continue;
    visited.insert(fx);
    FxSchematicNode *lastNode = node;
    assert(lastNode);
    FxSchematicPort *lastPort = lastNode->getLinkPort();
    assert(lastPort);
    for (fx = fx->getLinkedFx(); fx != it.key(); fx = fx->getLinkedFx()) {
      assert(visited.count(fx) == 0);
      if (visited.count(fx) > 0) break;
      visited.insert(fx);
      QMap<TFx *, FxSchematicNode *>::iterator h;
      h = fx2node.find(fx);
      if (h == fx2node.end()) continue;

      assert(h != fx2node.end());
      FxSchematicNode *node = h.value();
      assert(node);
      FxSchematicPort *port = node->getLinkPort();
      assert(port);
      if (port && lastPort) port->makeLink(lastPort);
      lastNode = node;
      lastPort = port;
    }
  }
}

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

QGraphicsItem *FxSchematicScene::getCurrentNode() {
  QList<QGraphicsItem *> allItems = items();

  for (auto const item : allItems) {
    FxSchematicNode *node = dynamic_cast<FxSchematicNode *>(item);
    if (node && node->getFx() == m_fxHandle->getFx()) return node;
  }
  return 0;
}

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

void FxSchematicScene::onSelectionSwitched(TSelection *oldSel,
                                           TSelection *newSel) {
  if (m_selection == oldSel && m_selection != newSel) clearSelection();
}

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

void FxSchematicScene::onSelectionChanged() {
  m_selection->selectNone();
  int i, size = m_highlightedLinks.size();
  for (i = 0; i < size; i++) {
    SchematicLink *link = m_highlightedLinks[i];
    link->setHighlighted(false);
    link->update();
  }
  m_highlightedLinks.clear();
  QList<QGraphicsItem *> slcItems = selectedItems();
  QList<QGraphicsItem *>::iterator it;
  for (it = slcItems.begin(); it != slcItems.end(); it++) {
    FxSchematicNode *node = dynamic_cast<FxSchematicNode *>(*it);
    if (node) {
      if (!node->isA(eGroupedFx)) {
        if (node->isA(eXSheetFx)) continue;
        m_selection->select(node->getFx());
        if (node->isA(eColumnFx)) {
          FxSchematicColumnNode *columnNode =
              dynamic_cast<FxSchematicColumnNode *>(node);
          if (columnNode)
            m_selection->select(columnNode->getColumnIndex());
          else {
            FxSchematicPaletteNode *paletteNode =
                dynamic_cast<FxSchematicPaletteNode *>(node);
            if (paletteNode) m_selection->select(paletteNode->getColumnIndex());
          }
        }
      } else {
        FxGroupNode *groupNode = dynamic_cast<FxGroupNode *>(node);
        assert(groupNode);
        QList<TFxP> fxs = groupNode->getGroupedFxs();
        for (i = 0; i < fxs.size(); i++) {
          m_selection->select(fxs[i].getPointer());
          TLevelColumnFx *colFx =
              dynamic_cast<TLevelColumnFx *>(fxs[i].getPointer());
          if (colFx) {
            if (TXshLevelColumn *column = colFx->getColumn()) {
              int colIndex = column->getIndex();
              m_selection->select(colIndex);
            }
          }
        }
      }
      highlightLinks(node, true);
    }
    SchematicLink *link = dynamic_cast<SchematicLink *>(*it);
    if (link) m_selection->select(link);
  }
  m_selection->makeCurrent();
  TSelectionHandle *selHandle = TSelectionHandle::getCurrent();
  selHandle->notifySelectionChanged();
}

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

void FxSchematicScene::reorderScene() {
  int step = m_gridDimension == eLarge ? 100 : 50;
  m_placedFxs.clear();
  QPointF sceneCenter = sceneRect().center();
  double minY         = sceneCenter.y();
  double maxX         = sceneCenter.x();
  double y            = minY;
  double x            = maxX;

  TXsheet *xsh = m_xshHandle->getXsheet();
  int i        = 0;

  FxDag *fxDag  = xsh->getFxDag();
  TFxSet *fxSet = fxDag->getInternalFxs();

  //  Let's reset every position to nowhere first
  fxDag->getXsheetFx()->getAttributes()->setDagNodePos(TConst::nowhere);

  for (i = 0; i < fxDag->getOutputFxCount(); i++) {
    TOutputFx *fx = fxDag->getOutputFx(i);
    if (!fx) continue;
    fx->getAttributes()->setDagNodePos(TConst::nowhere);
  }

  for (i = 0; i < xsh->getColumnCount(); i++) {
    TXshColumn *column = xsh->getColumn(i);
    TFx *fx            = column->getFx();
    if (!fx) continue;
    fx->getAttributes()->setDagNodePos(TConst::nowhere);
  }

  for (i = 0; i < fxSet->getFxCount(); i++) {
    TFx *fx = fxSet->getFx(i);
    fx->getAttributes()->setDagNodePos(TConst::nowhere);
    TMacroFx *macro = dynamic_cast<TMacroFx *>(fx);
    if (macro && macro->isEditing()) {
      std::vector<TFxP> fxs = macro->getFxs();
      int j;
      for (j = 0; j < (int)fxs.size(); j++) {
        fxs[j]->getAttributes()->setDagNodePos(TConst::nowhere);
      }
    }
  }

  // Let's start placing them now
  for (i = 0; i < xsh->getColumnCount(); i++) {
    TXshColumn *column = xsh->getColumn(i);
    TFx *fx            = column->getFx();

    if (column->isEmpty() || !fx) continue;

    TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx);
    if (zfx && (zfx->getZeraryFx()->getInputPortCount() > 0)) {
      TFxPort *port = zfx->getZeraryFx()->getInputPort(0);
      if (port && port->getFx()) continue;
    }

    if (zfx && m_placedFxs.contains(zfx->getZeraryFx())) continue;

    x = sceneCenter.x();
    placeNodeAndParents(fx, x, maxX, minY);
    y -= step;
    minY = std::min(y, minY);
  }

  // remove retrolink
  for (i = 0; i < xsh->getColumnCount(); i++) {
    TXshColumn *column = xsh->getColumn(i);
    TFx *fx            = column->getFx();
    if (column->isEmpty() || !fx) continue;

    TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx);
    if (zfx && m_placedFxs.contains(zfx->getZeraryFx())) continue;

    if (zfx && (zfx->getZeraryFx()->getInputPortCount() > 0)) {
      TFxPort *port = zfx->getZeraryFx()->getInputPort(0);
      if (port && port->getFx()) continue;
    }

    for (int j = 0; j < fx->getOutputConnectionCount(); j++) {
      TFx *outFx = fx->getOutputConnection(j)->getOwnerFx();
      removeRetroLinks(outFx, maxX);
    }
  }

  double middleY = (sceneCenter.y() + minY + step) * 0.5;
  placeNodeAndParents(xsh->getFxDag()->getXsheetFx(), maxX, maxX, middleY);
  y -= step;
  minY = std::min(y, minY);

  for (i = 0; i < fxSet->getFxCount(); i++) {
    TFx *fx = fxSet->getFx(i);
    if (m_placedFxs.contains(fx)) continue;

    placeNodeAndParents(fx, (sceneCenter.x() + 120), maxX, minY);
    y -= step;
    minY = std::min(y, minY);
  }
  updateScene();
}

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

void FxSchematicScene::removeRetroLinks(TFx *fx, double &maxX) {
  if (!fx) return;
  for (int i = 0; i < fx->getInputPortCount(); i++) {
    TFx *inFx = fx->getInputPort(i)->getFx();
    if (!inFx) continue;
    TPointD inFxPos = inFx->getAttributes()->getDagNodePos();
    TPointD fxPos   = fx->getAttributes()->getDagNodePos();
    if (inFxPos != TConst::nowhere && fxPos != TConst::nowhere &&
        fxPos.x <= inFxPos.x) {
      while (fxPos.x <= inFxPos.x) fxPos.x += 150;
      maxX = std::max(fxPos.x + 150, maxX);
      fx->getAttributes()->setDagNodePos(fxPos);
      for (int j = 0; j < fx->getOutputConnectionCount(); j++) {
        TFx *outFx = fx->getOutputConnection(j)->getOwnerFx();
        removeRetroLinks(outFx, maxX);
      }
    }
  }
}

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

void FxSchematicScene::placeNodeAndParents(TFx *fx, double x, double &maxX,
                                           double &minY) {
  int step = m_gridDimension == eLarge ? 100 : 50;
  if (!fx) return;
  m_placedFxs.append(fx);
  if (fx->getFxType() == "STD_particlesFx" ||
      fx->getFxType() == "STD_Iwa_ParticlesFx") {
    TXsheet *xsh = m_xshHandle->getXsheet();
    int i        = 0;
    for (i = 0; i < xsh->getColumnCount(); i++) {
      TFx *columnFx        = xsh->getColumn(i)->getFx();
      TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(columnFx);
      if (zfx && zfx->getZeraryFx() == fx) {
        fx = zfx;
        break;
      }
    }
  }
  double y        = minY;
  TMacroFx *macro = dynamic_cast<TMacroFx *>(fx);
  if (macro) {
    int tmpY              = y;
    std::vector<TFxP> fxs = macro->getFxs();
    for (int j = 0; j < (int)fxs.size(); j++) {
      TFx *macroFx = fxs[j].getPointer();
      if (macroFx && !m_placedFxs.contains(macroFx)) {
        placeNodeAndParents(macroFx, x, maxX, minY);
        y -= step;
        minY = std::min(y, minY);
      }
    }
    tmpY = (minY + tmpY + step) * 0.5;
    fx->getAttributes()->setDagNodePos(TPointD(x, tmpY));
  } else
    fx->getAttributes()->setDagNodePos(TPointD(x, y));
  if (fx->getOutputConnectionCount() == 0) minY -= step;
  x += 120;
  maxX = std::max(maxX, x);
  int i;
  for (i = 0; i < fx->getOutputConnectionCount(); i++) {
    TFx *outputFx = fx->getOutputConnection(i)->getOwnerFx();
    // controllo se e' una porta sorgente
    TFxPort *port = outputFx->getInputPort(0);
    if (port && port->getFx() != fx) continue;
    if (!m_placedFxs.contains(outputFx) ||
        outputFx->getAttributes()->getDagNodePos().x < x) {
      placeNodeAndParents(outputFx, x, maxX, minY);
      y -= step;
      minY = std::min(y, minY);
    }
  }
}

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

void FxSchematicScene::onDisconnectFromXSheet() {
  std::list<TFxP, std::allocator<TFxP>> list(m_selection->getFxs().begin(),
                                             m_selection->getFxs().end());
  TFxCommand::disconnectNodesFromXsheet(list, m_xshHandle);
}

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

void FxSchematicScene::onConnectToXSheet() {
  std::list<TFxP, std::allocator<TFxP>> list(m_selection->getFxs().begin(),
                                             m_selection->getFxs().end());
  TFxCommand::connectNodesToXsheet(list, m_xshHandle);
}

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

void FxSchematicScene::onDeleteFx() {
  std::list<TFxP, std::allocator<TFxP>> fxList(m_selection->getFxs().begin(),
                                               m_selection->getFxs().end());
  std::list<TFxCommand::Link> linkList(m_selection->getLinks().begin(),
                                       m_selection->getLinks().end());
  std::list<int> columnIndexList(m_selection->getColumnIndexes().begin(),
                                 m_selection->getColumnIndexes().end());
  TFxCommand::deleteSelection(fxList, linkList, columnIndexList, m_xshHandle,
                              m_fxHandle);
}

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

void FxSchematicScene::onDuplicateFx() {
  QList<TFxP> fxs = m_selection->getFxs();
  if (fxs.empty()) return;

  TUndoManager::manager()->beginBlock();

  int i, size = fxs.size();
  for (i = 0; i != size; ++i)
    TFxCommand::duplicateFx(fxs[i].getPointer(), m_xshHandle, m_fxHandle);

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

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

void FxSchematicScene::onUnlinkFx() {
  QList<TFxP> fxs = m_selection->getFxs();
  if (fxs.empty()) return;

  TUndoManager::manager()->beginBlock();

  int i, size = fxs.size();
  for (i = 0; i != size; ++i)
    TFxCommand::unlinkFx(fxs[i].getPointer(), m_fxHandle, m_xshHandle);

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

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

void FxSchematicScene::onMacroFx() {
  TFxCommand::makeMacroFx(std::vector<TFxP>(m_selection->getFxs().begin(),
                                            m_selection->getFxs().end()),
                          m_app);
}

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

void FxSchematicScene::onExplodeMacroFx() {
  if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(m_fxHandle->getFx()))
    TFxCommand::explodeMacroFx(macroFx, m_app);
}

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

void FxSchematicScene::onOpenMacroFx() {
  if (TMacroFx *macroFx = dynamic_cast<TMacroFx *>(m_fxHandle->getFx())) {
    macroFx->editMacro(true);
    updateScene();
  }
}

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

void FxSchematicScene::onSavePresetFx() {
  CommandManager::instance()->getAction("MI_SavePreset")->trigger();
}

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

void FxSchematicScene::onActivateOutput() {
  TFxCommand::makeOutputFxCurrent(m_fxHandle->getFx(), m_xshHandle);
}

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

void FxSchematicScene::onPreview() { emit showPreview(m_fxHandle->getFx()); }

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

void FxSchematicScene::onCacheFx() { setEnableCache(true); }

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

void FxSchematicScene::onUncacheFx() { setEnableCache(false); }

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

void FxSchematicScene::setEnableCache(bool toggle) {
  QList<TFxP> selectedFxs = m_selection->getFxs();
  for (int i = 0; i < selectedFxs.size(); i++) {
    TFx *fx               = selectedFxs[i].getPointer();
    TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx);
    if (zcfx) fx = zcfx->getZeraryFx();
    TFxAttributes *attr = fx->getAttributes();
    if (!attr->isGrouped() || attr->isGroupEditing()) {
      if (toggle) {
        TPassiveCacheManager::instance()->enableCache(fx);
      } else {
        TPassiveCacheManager::instance()->disableCache(fx);
      }
    } else {
      QMap<int, FxGroupNode *>::iterator it;
      for (it = m_groupedTable.begin(); it != m_groupedTable.end(); it++) {
        FxGroupNode *group = it.value();
        QList<TFxP> roots  = group->getRootFxs();
        for (int j = 0; j < roots.size(); j++) {
          if (fx == roots[j].getPointer()) {
            if (toggle) {
              TPassiveCacheManager::instance()->enableCache(fx);
            } else {
              TPassiveCacheManager::instance()->disableCache(fx);
            }
          }
        }
        group->update();
      }
    }
  }
}

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

void FxSchematicScene::onCollapse(const QList<TFxP> &fxs) {
  emit doCollapse(fxs);
}

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

TXsheet *FxSchematicScene::getXsheet() { return m_xshHandle->getXsheet(); }

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

void FxSchematicScene::onXsheetChanged() { m_xshHandle->notifyXsheetChanged(); }

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

void FxSchematicScene::onSceneChanged() {
  m_app->getCurrentScene()->notifySceneChanged();
}

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

void FxSchematicScene::onSwitchCurrentFx(TFx *fx) {
  if (m_fxHandle->getFx() == fx) return;
  if (fx) {
    // Forbid update of the swatch upon column switch. This could generate
    // a further useless render...
    SwatchViewer::suspendRendering(true, false);

    int columnIndex = fx->getReferenceColumnIndex();
    if (columnIndex >= 0) {
      m_columnHandle->setColumnIndex(columnIndex);
      m_app->getCurrentObject()->setObjectId(
          TStageObjectId::ColumnId(columnIndex));
    }

    SwatchViewer::suspendRendering(false);

    m_fxHandle->setFx(fx, false);  // Setting the fx updates the swatch

    emit editObject();
  } else {
    m_fxHandle->setFx(0, false);
  }
}

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

void FxSchematicScene::onFxNodeDoubleClicked() {
  // emitting fxSettingsShouldBeSwitched
  m_fxHandle->onFxNodeDoubleClicked();
}

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

void FxSchematicScene::onCurrentFxSwitched() {
  if (m_currentFxNode) m_currentFxNode->setIsCurrentFxLinked(false, 0);
  if (m_table.contains(m_fxHandle->getFx())) {
    m_currentFxNode = m_table[m_fxHandle->getFx()];
    m_currentFxNode->setIsCurrentFxLinked(true, 0);
  } else
    m_currentFxNode = 0;
}

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

void FxSchematicScene::onCurrentColumnChanged(int index) {
  m_app->getCurrentColumn()->setColumnIndex(index);
  m_app->getCurrentObject()->setObjectId(TStageObjectId::ColumnId(index));
}

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

void FxSchematicScene::onIconifyNodesToggled(bool iconified) {
  m_isNormalIconView      = !iconified;
  IconifyFxSchematicNodes = (iconified) ? 1 : 0;
  updateScene();
}

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

TFx *FxSchematicScene::getCurrentFx() { return m_fxHandle->getFx(); }

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

void FxSchematicScene::mousePressEvent(QGraphicsSceneMouseEvent *me) {
  QList<QGraphicsItem *> items = selectedItems();
  QGraphicsItem *item          = itemAt(me->scenePos(), QTransform());
  FxSchematicPort *port        = dynamic_cast<FxSchematicPort *>(item);
  FxSchematicLink *link        = dynamic_cast<FxSchematicLink *>(item);
  SchematicScene::mousePressEvent(me);
  onSelectionChanged();
  if (me->button() == Qt::MiddleButton) {
    int i;
    for (i = 0; i < items.size(); i++) items[i]->setSelected(true);
  }
  /*
  m_selection may not be updated here, so I use QGraphicsScene::selectedItems()
  instead of m_selection->isEmpty() to check whether any node is selected or
  not.
  */
  if (selectedItems().isEmpty()) {
    if (me->button() != Qt::MiddleButton && !item) m_fxHandle->setFx(0, false);
    return;
  }
  m_isConnected = false;
  if (!canDisconnectSelection(m_selection)) return;
  m_selectionOldPos.clear();
  QList<TFxP> selectedFxs = m_selection->getFxs();
  int i;
  for (i = 0; i < selectedFxs.size(); i++) {
    TFxP selectedFx = selectedFxs[i];
    TPointD pos     = selectedFx->getAttributes()->getDagNodePos();
    m_selectionOldPos.append(QPair<TFxP, TPointD>(selectedFx, pos));
  }
  FxsData fxsData;
  fxsData.setFxs(m_selection->getFxs(), m_selection->getLinks(),
                 m_selection->getColumnIndexes(), m_xshHandle->getXsheet());
  // m_isConnected indicates that the all selected nodes are connected
  if (fxsData.isConnected() && me->button() == Qt::LeftButton && !port && !link)
    m_isConnected = true;
}

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

void FxSchematicScene::mouseMoveEvent(QGraphicsSceneMouseEvent *me) {
  SchematicScene::mouseMoveEvent(me);

  m_lastPos = me->scenePos();

  bool leftButton = (QApplication::mouseButtons() == Qt::LeftButton);
  bool altButton  = (QApplication::keyboardModifiers() == Qt::AltModifier);

  if (leftButton && m_isConnected && altButton) {
    m_linkUnlinkSimulation = true;

    simulateDisconnectSelection(true);
    m_connectionLinks.showBridgeLinks();

    SchematicLink *link =
        dynamic_cast<SchematicLink *>(itemAt(m_lastPos, QTransform()));
    if (link && (link->getEndPort() && link->getStartPort())) {
      TFxCommand::Link fxLink = m_selection->getBoundingFxs(link);
      if (fxLink == TFxCommand::Link()) return;

      TFx *inputFx  = fxLink.m_inputFx.getPointer();
      TFx *outputFx = fxLink.m_outputFx.getPointer();

      TFxSet *internalFxs = getXsheet()->getFxDag()->getInternalFxs();
      if (!internalFxs->containsFx(inputFx) &&
          !dynamic_cast<TColumnFx *>(inputFx) &&
          !dynamic_cast<TXsheetFx *>(inputFx) &&
          !dynamic_cast<TOutputFx *>(inputFx))
        return;
      if (!internalFxs->containsFx(outputFx) &&
          !dynamic_cast<TColumnFx *>(outputFx) &&
          !dynamic_cast<TXsheetFx *>(outputFx) &&
          !dynamic_cast<TOutputFx *>(outputFx))
        return;
    }

    m_connectionLinks.hideBridgeLinks();
    simulateInsertSelection(link, altButton && !!link);
  }
}

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

void FxSchematicScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *me) {
  SchematicScene::mouseReleaseEvent(me);

  m_linkUnlinkSimulation = false;

  if ((m_disconnectionLinks.size() == 0) && (m_connectionLinks.size() == 0))
    return;

  TUndoManager::manager()->beginBlock();

  bool altButton = QApplication::keyboardModifiers() == Qt::AltModifier;
  if (altButton && m_isConnected) {
    if (m_connectionLinks.size() > 0) {
      const QList<SchematicLink *> &bridgeLinks =
          m_connectionLinks.getBridgeLinks();
      assert(bridgeLinks.size() <= 1);

      SchematicLink *link = bridgeLinks[0];

      if (link) {
        FxSchematicNode *outputNode =
            dynamic_cast<FxSchematicNode *>(link->getEndPort()->getNode());
        FxSchematicNode *inputNode =
            dynamic_cast<FxSchematicNode *>(link->getStartPort()->getNode());

        if (inputNode && outputNode) {
          SchematicPort *port = link->getStartPort();
          if (port->getType() == 200 || port->getType() == 204)
            port = link->getOtherPort(port);

          int i;
          for (i = 0; i < outputNode->getInputPortCount(); i++)
            if (port == outputNode->getInputPort(i)) break;

          TFxCommand::Link fxLink;
          fxLink.m_outputFx = outputNode->getFx();
          fxLink.m_inputFx  = inputNode->getFx();
          if (!outputNode->isA(eXSheetFx)) fxLink.m_index = i;

          TFxCommand::connectFxs(fxLink,
                                 std::list<TFxP>(m_selection->getFxs().begin(),
                                                 m_selection->getFxs().end()),
                                 m_xshHandle, m_selectionOldPos);
        }
      }
    } else if (m_disconnectionLinks.size() > 0) {
      TFxCommand::disconnectFxs(std::list<TFxP>(m_selection->getFxs().begin(),
                                                m_selection->getFxs().end()),
                                m_xshHandle, m_selectionOldPos);
      m_selectionOldPos.clear();
    }
  }

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

  m_isConnected = false;
}

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

bool FxSchematicScene::event(QEvent *e) {
  bool ret        = SchematicScene::event(e);
  bool altPressed = QApplication::keyboardModifiers() == Qt::AltModifier;
  if (m_altPressed != altPressed) {
    // When Alt key is pressed, put the link lines on top of other items
    // in order to enable to pick them up with itemAt() function.
    double z                          = (altPressed) ? 5.0 : 0.0;
    QList<QGraphicsItem *> sceneItems = items();
    for (int i = 0; i < sceneItems.size(); i++) {
      SchematicLink *link = dynamic_cast<SchematicLink *>(sceneItems.at(i));
      if (link) link->setZValue(z);
    }

    if (m_linkUnlinkSimulation) onAltModifierChanged(altPressed);
    m_altPressed = altPressed;
  }
  return ret;
}

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

void FxSchematicScene::onInsertPaste() {
  if (!m_selection->insertPasteSelection())
    DVGui::error(
        tr("Cannot Paste Insert a selection of unconnected FX nodes.\nSelect "
           "FX nodes and related links before copying or cutting the selection "
           "you want to paste."));
}

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

void FxSchematicScene::onAddPaste() {
  if (!m_selection->addPasteSelection())
    DVGui::error(
        tr("Cannot Paste Add a selection of unconnected FX nodes.\nSelect FX "
           "nodes and related links before copying or cutting the selection "
           "you want to paste."));
}

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

void FxSchematicScene::onReplacePaste() {
  if (!m_selection->replacePasteSelection())
    DVGui::error(
        tr("Cannot Paste Replace a selection of unconnected FX nodes.\nSelect "
           "FX nodes and related links before copying or cutting the selection "
           "you want to paste."));
}

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

void FxSchematicScene::onAltModifierChanged(bool altPressed) {
  if (altPressed) {
    if (m_disconnectionLinks.size() == 0 && m_linkUnlinkSimulation)
      simulateDisconnectSelection(altPressed);
    if (m_connectionLinks.size() == 0 && m_linkUnlinkSimulation) {
      SchematicLink *link =
          dynamic_cast<SchematicLink *>(itemAt(m_lastPos, QTransform()));
      if (link && (!link->getEndPort() || !link->getStartPort())) return;
      simulateInsertSelection(link, altPressed && !!link);
    }
  } else {
    if (m_disconnectionLinks.size() > 0 && m_linkUnlinkSimulation)
      simulateDisconnectSelection(altPressed);
    if (m_connectionLinks.size() > 0 && m_linkUnlinkSimulation) {
      m_connectionLinks.showBridgeLinks();
      simulateInsertSelection(0, false);
    }
  }
}

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

void FxSchematicScene::onEditGroup() {
  if (m_selection->isEmpty()) return;
  QList<TFxP> fxs = m_selection->getFxs();
  int i;
  for (i = 0; i < fxs.size(); i++) {
    if (fxs[i]->getAttributes()->isGrouped() &&
        !fxs[i]->getAttributes()->isGroupEditing()) {
      fxs[i]->getAttributes()->editGroup();
      TMacroFx *macro = dynamic_cast<TMacroFx *>(fxs[i].getPointer());
      if (macro) {
        std::vector<TFxP> macroFxs = macro->getFxs();
        int j;
        for (j = 0; j < (int)macroFxs.size(); j++)
          macroFxs[j]->getAttributes()->editGroup();
      }
    }
  }
  updateScene();
}

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

void FxSchematicScene::highlightLinks(FxSchematicNode *node, bool value,
                                      SearchDirection direction) {
  int i;
  if (direction == Both || direction == Input) {
    int portCount = node->getInputPortCount();
    // SchematicLink* ghostLink = m_supportLinks.getDisconnectionLink(eGhost);
    for (i = 0; i < portCount; i++) {
      FxSchematicPort *port = node->getInputPort(i);
      int j, linkCount = port->getLinkCount();
      for (j = 0; j < linkCount; j++) {
        SchematicLink *link = port->getLink(j);
        if (!link) continue;
        if (m_disconnectionLinks.isABridgeLink(link)) continue;
        link->setHighlighted(value);
        link->update();
        m_highlightedLinks.push_back(link);

        if (FxSchematicPassThroughNode *ptNode =
                dynamic_cast<FxSchematicPassThroughNode *>(
                    link->getOtherNode(node)))
          highlightLinks(ptNode, value, Input);
      }
    }
  }

  if (direction == Both || direction == Output) {
    FxSchematicPort *port = node->getOutputPort();
    if (port) {
      int linkCount = port->getLinkCount();
      for (i = 0; i < linkCount; i++) {
        SchematicLink *link = port->getLink(i);
        if (!link) continue;
        if (m_disconnectionLinks.isABridgeLink(link)) continue;
        link->setHighlighted(value);
        link->update();
        m_highlightedLinks.push_back(link);

        if (FxSchematicPassThroughNode *ptNode =
                dynamic_cast<FxSchematicPassThroughNode *>(
                    link->getOtherNode(node)))
          highlightLinks(ptNode, value, Output);
      }
    }
  }

  if (direction != Both) return;

  FxSchematicPort *port = node->getLinkPort();
  if (port) {
    SchematicLink *link = port->getLink(0);
    if (link && !m_disconnectionLinks.isABridgeLink(link)) {
      link->setHighlighted(value);
      link->update();
      m_highlightedLinks.push_back(link);
    }
  }
}

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

void FxSchematicScene::simulateDisconnectSelection(bool disconnect) {
  if (disconnect) {
    if (m_selection->isEmpty()) return;
    QList<TFxP> selectedFxs = m_selection->getFxs();
    if (selectedFxs.isEmpty()) return;
    QMap<TFx *, bool> visitedFxs;
    int i;
    for (i = 0; i < selectedFxs.size(); i++)
      visitedFxs[selectedFxs[i].getPointer()] = false;

    TFx *inputFx = 0, *outputFx = 0;
    findBoundariesFxs(inputFx, outputFx, visitedFxs);
    FxSchematicNode *inputNode  = m_table[inputFx];
    FxSchematicNode *outputNode = m_table[outputFx];
    assert(inputNode && outputNode);

    FxSchematicPort *inputPort = 0, *outputPort = 0;
    SchematicPort *otherInputPort = 0;
    QList<SchematicPort *> otherOutputPorts;
    if (inputNode->getInputPortCount() > 0) {
      inputPort = inputNode->getInputPort(0);
      if (inputPort) {
        SchematicLink *inputLink = inputPort->getLink(0);
        if (inputLink && !m_connectionLinks.isAnInputLink(inputLink)) {
          if (!m_disconnectionLinks.isAnInputLink(inputLink))
            m_disconnectionLinks.addInputLink(inputLink);
          otherInputPort = inputLink->getOtherPort(inputPort);
        }
      }
    }
    outputPort = outputNode->getOutputPort();
    if (outputPort) {
      for (i = 0; i < outputPort->getLinkCount(); i++) {
        SchematicLink *outputLink = outputPort->getLink(i);
        if (outputLink && !m_connectionLinks.isAnOutputLink(outputLink)) {
          if (!m_disconnectionLinks.isAnOutputLink(outputLink))
            m_disconnectionLinks.addOutputLink(outputLink);
          otherOutputPorts.push_back(outputLink->getOtherPort(outputPort));
        }
      }
    }
    m_disconnectionLinks.hideInputLinks();
    m_disconnectionLinks.hideOutputLinks();

    if (otherInputPort) {
      for (i = 0; i < otherOutputPorts.size(); i++)
        m_disconnectionLinks.addBridgeLink(
            otherOutputPorts[i]->makeLink(otherInputPort));
    }
  } else {
    m_disconnectionLinks.showInputLinks();
    m_disconnectionLinks.showOutputLinks();
    m_disconnectionLinks.removeInputLinks();
    m_disconnectionLinks.removeOutputLinks();
    m_disconnectionLinks.removeBridgeLinks(true);
  }
}

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

void FxSchematicScene::simulateInsertSelection(SchematicLink *link,
                                               bool connect) {
  // first, remove all connected links
  m_connectionLinks.showBridgeLinks();
  m_connectionLinks.hideInputLinks();
  m_connectionLinks.hideOutputLinks();
  m_connectionLinks.removeBridgeLinks();
  m_connectionLinks.removeInputLinks(true);
  m_connectionLinks.removeOutputLinks(true);
  if (!link || !connect) return;

  if (m_disconnectionLinks.isABridgeLink(link) || m_selection->isEmpty())
    return;

  m_connectionLinks.addBridgeLink(link);
  m_connectionLinks.hideBridgeLinks();

  SchematicPort *inputPort = 0, *outputPort = 0;
  if (link) {
    if (link->getStartPort()->getType() == eFxInputPort) {
      inputPort  = link->getStartPort();
      outputPort = link->getEndPort();
    } else {
      inputPort  = link->getEndPort();
      outputPort = link->getStartPort();
    }
  }

  QMap<TFx *, bool> visitedFxs;
  QList<TFxP> selectedFxs = m_selection->getFxs();
  if (selectedFxs.isEmpty()) return;
  int i;
  for (i = 0; i < selectedFxs.size(); i++)
    visitedFxs[selectedFxs[i].getPointer()] = false;

  TFx *inputFx = 0, *outputFx = 0;
  findBoundariesFxs(inputFx, outputFx, visitedFxs);
  FxSchematicNode *inputNode  = m_table[inputFx];
  FxSchematicNode *outputNode = m_table[outputFx];
  assert(inputNode && outputNode);

  if (inputNode->getInputPortCount() > 0) {
    SchematicPort *inputNodePort = inputNode->getInputPort(0);
    if (inputNodePort && outputPort)
      m_connectionLinks.addInputLink(inputNodePort->makeLink(outputPort));
  }

  SchematicPort *outputNodePort = outputNode->getOutputPort();
  if (outputNodePort && inputPort)
    m_connectionLinks.addOutputLink(inputPort->makeLink(outputNodePort));

  m_connectionLinks.showInputLinks();
  m_connectionLinks.showOutputLinks();
}

//------------------------------------------------------------
/*! in order to select nods after pasting the copied fx nodes from FxSelection
 */
void FxSchematicScene::selectNodes(QList<TFxP> &fxs) {
  clearSelection();
  for (int i = 0; i < (int)fxs.size(); i++) {
    TFx *tempFx = fxs[i].getPointer();

    QMap<TFx *, FxSchematicNode *>::iterator it;
    it = m_table.find(tempFx);
    if (it == m_table.end()) continue;

    it.value()->setSelected(true);
  }
  update();
}

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

void FxSchematicScene::updateNestedGroupEditors(FxSchematicNode *node,
                                                const QPointF &newPos) {
  if (!node) return;
  QStack<int> groupIdStack = node->getFx()->getAttributes()->getGroupIdStack();
  int i;
  QRectF rect;
  for (i = 0; i < groupIdStack.size(); i++) {
    if (m_groupEditorTable.contains(groupIdStack[i])) {
      QRectF app = m_groupEditorTable[groupIdStack[i]]->sceneBoundingRect();
      if (rect.isEmpty())
        rect = app;
      else
        rect = rect.united(app);
    }
  }
  QMap<TMacroFx *, FxSchematicMacroEditor *>::iterator it;
  for (it = m_macroEditorTable.begin(); it != m_macroEditorTable.end(); it++) {
    if (it.value()->contains(node)) {
      QRectF app = it.value()->sceneBoundingRect();
      if (rect.isEmpty())
        rect = app;
      else
        rect = rect.united(app);
    }
  }
  node->setPos(newPos);
  for (i = 0; i < groupIdStack.size(); i++) {
    if (!m_groupEditorTable.contains(groupIdStack[i])) continue;
    rect =
        rect.united(m_groupEditorTable[groupIdStack[i]]->sceneBoundingRect());
    QRectF app = m_groupEditorTable[groupIdStack[i]]->boundingSceneRect();
    if (m_groupEditorTable[groupIdStack[i]]->scenePos() != app.topLeft())
      m_groupEditorTable[groupIdStack[i]]->setPos(app.topLeft());
  }
  for (it = m_macroEditorTable.begin(); it != m_macroEditorTable.end(); it++) {
    FxSchematicMacroEditor *editor = it.value();
    if (editor->contains(node)) {
      QRectF app = editor->sceneBoundingRect();
      rect       = rect.united(app);
      app        = editor->boundingSceneRect();
      if (editor->scenePos() != app.topLeft()) editor->setPos(app.topLeft());
    }
  }
  update(rect);
}

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

void FxSchematicScene::closeInnerMacroEditor(int groupId) {
  assert(m_groupEditorTable.contains(groupId));
  QMap<TMacroFx *, FxSchematicMacroEditor *>::iterator it;
  for (it = m_macroEditorTable.begin(); it != m_macroEditorTable.end(); it++) {
    TMacroFx *macro = it.key();
    assert(macro);
    if (macro->getAttributes()->isContainedInGroup(groupId)) {
      macro->editMacro(false);
      macro->getAttributes()->closeEditingGroup(groupId);
    }
  }
}

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

void FxSchematicScene::resizeNodes(bool maximizedNode) {
  resizingNodes = true;

  // resize nodes
  m_gridDimension = maximizedNode ? eLarge : eSmall;
  m_xshHandle->getXsheet()->getFxDag()->setDagGridDimension(m_gridDimension);
  QMap<TFx *, FxSchematicNode *>::iterator it1;
  for (it1 = m_table.begin(); it1 != m_table.end(); it1++) {
    if (!it1.value()) continue;
    it1.value()->resize(maximizedNode);
    TFx *fx = it1.value()->getFx();
    updatePositionOnResize(fx, maximizedNode);
  }
  QMap<int, FxGroupNode *>::iterator it2;
  for (it2 = m_groupedTable.begin(); it2 != m_groupedTable.end(); it2++) {
    if (!it2.value()) continue;
    it2.value()->resize(maximizedNode);
    QList<TFxP> groupedFxs = it2.value()->getGroupedFxs();
    for (int i = 0; i < groupedFxs.size(); i++)
      updatePositionOnResize(groupedFxs[i].getPointer(), maximizedNode);
  }

  QMap<TMacroFx *, FxSchematicMacroEditor *>::iterator it3;
  for (it3 = m_macroEditorTable.begin(); it3 != m_macroEditorTable.end();
       it3++) {
    if (!it3.value()) continue;
    it3.value()->resizeNodes(maximizedNode);
  }
  updateScene();

  resizingNodes = false;
}

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

void FxSchematicScene::updatePositionOnResize(TFx *fx, bool maximizedNode) {
  TPointD oldPos = fx->getAttributes()->getDagNodePos();
  if (oldPos == TConst::nowhere) return;
  double oldPosY = oldPos.y - 25000;
  double newPosY = maximizedNode ? oldPosY * 2 : oldPosY * 0.5;
  fx->getAttributes()->setDagNodePos(TPointD(oldPos.x, newPosY + 25000));
}

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

void FxSchematicScene::onNodeChangedSize() {
  if (resizingNodes) return;
  updateScene();
}

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

bool FxSchematicScene::isAnEmptyZone_withParentFx(const QRectF &rect,
                                                  const TFx *parent) {
  QList<QGraphicsItem *> allItems = items();
  for (auto const level : allItems) {
    SchematicNode *node = dynamic_cast<SchematicNode *>(level);
    if (!node) continue;
    FxSchematicNode *fxNode = dynamic_cast<FxSchematicNode *>(node);
    if (fxNode && fxNode->isA(eXSheetFx)) continue;
    // check only the fxs sharing the same parent
    if (!fxNode) continue;
    TFx *fx = fxNode->getFx();
    if (TZeraryColumnFx *zfx = dynamic_cast<TZeraryColumnFx *>(fx))
      fx = zfx->getZeraryFx();
    if (!fx || fx == parent) continue;
    for (int p = 0; p < fx->getInputPortCount(); p++) {
      if (parent == fx->getInputPort(p)->getFx()) {
        if (node->boundingRect().translated(node->scenePos()).intersects(rect))
          return false;
        else
          break;
      }
    }
  }
  return true;
}

//------------------------------------------------------------------
// update snap targets on click node
void FxSchematicScene::updateSnapTarget(QGraphicsItem *item) {
  clearSnapTargets();
  FxSchematicNode *node = dynamic_cast<FxSchematicNode *>(item);
  if (!node) return;

  // find input connections
  int portCount = node->getInputPortCount();
  for (int i = 0; i < portCount; i++) {
    FxSchematicPort *port = node->getInputPort(i);
    int j, linkCount = port->getLinkCount();
    for (j = 0; j < linkCount; j++) {
      SchematicLink *link = port->getLink(j);
      if (!link) continue;
      if (m_disconnectionLinks.isABridgeLink(link)) continue;
      SchematicNode *otherNode = link->getOtherNode(node);
      if (otherNode && !otherNode->isSelected()) {
        QPointF targetPos =
            otherNode->scenePos() + QPointF(otherNode->boundingRect().width() +
                                                SchematicScene::snapHSpacing,
                                            0);

        addSnapTarget(targetPos, node->boundingRect(),
                      link->getOtherPort(port)->getLinkEndPoint(),
                      port->getLinkEndPoint() - node->scenePos());
      }
    }
  }

  // find output connections
  FxSchematicPort *port = node->getOutputPort();
  if (port) {
    int linkCount = port->getLinkCount();
    for (int i = 0; i < linkCount; i++) {
      SchematicLink *link = port->getLink(i);
      if (!link) continue;
      if (m_disconnectionLinks.isABridgeLink(link)) continue;
      SchematicNode *otherNode = link->getOtherNode(node);
      if (otherNode && !otherNode->isSelected()) {
        QPointF targetPos =
            otherNode->scenePos() -
            QPointF(node->boundingRect().width() + SchematicScene::snapHSpacing,
                    0);
        addSnapTarget(targetPos, node->boundingRect(),
                      link->getOtherPort(port)->getLinkEndPoint(),
                      port->getLinkEndPoint() - node->scenePos());
      }
    }
  }
}