Blob Blame Raw


// TnzCore includes
#include "tstream.h"

// TnzBase includes
#include "toutputproperties.h"
#include "tfx.h"
#include "tparamcontainer.h"
#include "tparamset.h"
#include "tfxattributes.h"

// TnzLib includes
#include "toonz/fxdag.h"
#include "toonz/txshchildlevel.h"
#include "toonz/txshcell.h"
#include "toonz/observer.h"
#include "toonz/controlpointobserver.h"
#include "toonz/tcolumnfx.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshpalettecolumn.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/txshsoundcolumn.h"
#include "toonz/sceneproperties.h"
#include "toonz/toonzscene.h"
#include "toonz/columnfan.h"
#include "toonz/txshleveltypes.h"
#include "toonz/txshnoteset.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/stage.h"
#include "toonz/textureutils.h"
#include "xshhandlemanager.h"

#include "toonz/txsheet.h"

using namespace std;

DEFINE_CLASS_CODE(TXsheet, 18)

//-----------------------------------------------------------------------------
namespace
{
//-----------------------------------------------------------------------------

string getColumnDefaultName(TXsheet *xsh, int col, QString oldName)
{
	TXshColumn *column = xsh->getColumn(col);
	if (column) {
		TXshLevelColumn *lc = column->getLevelColumn();
		if (lc) {
			int r0, r1;
			if (lc->getRange(r0, r1)) {
				TXshCell cell = lc->getCell(r0);
				assert(!cell.isEmpty());
				TXshLevel *level = cell.m_level.getPointer();
				if (level) {
					bool isNumber = true;
					oldName.right(oldName.size() - 3).toInt(&isNumber);
					if (oldName.left(3) == "Col" && isNumber)
						return toString(level->getName());
					else
						return "";
				}
			}
		}
	}
	return "Col" + toString(col + 1);
}

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

void setColumnName(TXsheet *xsh, int col)
{
	TStageObject *obj = xsh->getStageObject(TStageObjectId::ColumnId(col));
	QString oldName = QString::fromStdString(obj->getName());
	string name = getColumnDefaultName(xsh, col, oldName);
	if (!name.empty())
		obj->setName(name);
}

} // namespace

//=============================================================================
// TXsheetImp

struct TXsheet::TXsheetImp {
	unsigned long m_id; //!< The xsheet instance's unique identifier

	TColumnSetT<TXshColumn> m_columnSet;
	TStageObjectTree *m_pegTree;
	FxDag *m_fxDag;

	int m_frameCount;
	int m_soloColumn;
	int m_viewColumn;

	TSoundTrackP m_mixedSound;
	ColumnFan m_columnFan;
	XshHandleManager *m_handleManager;
	ToonzScene *m_scene;

public:
	TXsheetImp();
	~TXsheetImp();

	static inline unsigned long newIdentifier()
	{
		static unsigned long currentId = 0;
		return ++currentId;
	}

private:
	//not implemented
	TXsheetImp(const TXsheetImp &);
	TXsheetImp &operator=(const TXsheetImp &);
};

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

TXsheet::SoundProperties::SoundProperties()
	: m_fromFrame(-1), m_toFrame(-1), m_frameRate(-1), m_isPreview(false)
{
}

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

TXsheet::SoundProperties::~SoundProperties() {}

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

inline bool TXsheet::SoundProperties::operator==(const SoundProperties &c) const
{
	return m_fromFrame == c.m_fromFrame &&
		   m_toFrame == c.m_toFrame &&
		   m_frameRate == c.m_frameRate &&
		   m_isPreview == c.m_isPreview;
}

inline bool TXsheet::SoundProperties::operator!=(const SoundProperties &c) const
{
	return !(*this == c);
}

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

TXsheet::TXsheetImp::TXsheetImp()
	: m_id(newIdentifier()), m_pegTree(new TStageObjectTree), m_handleManager(0), m_fxDag(new FxDag()), m_frameCount(0), m_soloColumn(-1), m_viewColumn(-1), m_mixedSound(0), m_scene(0)
{
}

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

TXsheet::TXsheetImp::~TXsheetImp()
{
	assert(m_pegTree);
	assert(m_fxDag);
	assert(m_handleManager);
	delete m_pegTree;
	delete m_fxDag;
	delete m_handleManager;
}

//=============================================================================
// TXsheet

TXsheet::TXsheet()
	: TSmartObject(m_classCode)
	, m_player(0)
	, m_imp(new TXsheet::TXsheetImp)
	, m_notes(new TXshNoteSet())
{
	//extern TSyntax::Grammar *createXsheetGrammar(TXsheet*);
	m_soundProperties = new TXsheet::SoundProperties();
	m_imp->m_handleManager = new XshHandleManager(this);
	m_imp->m_pegTree->setHandleManager(m_imp->m_handleManager);
	m_imp->m_pegTree->createGrammar(this);
}

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

TXsheet::~TXsheet()
{
	texture_utils::invalidateTextures(this);

	assert(m_imp);
	if (m_notes)
		delete m_notes;
	if (m_soundProperties)
		delete m_soundProperties;
}

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

unsigned long TXsheet::id() const
{
	return m_imp->m_id;
}

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

int TXsheet::getFrameCount() const
{
	return m_imp->m_frameCount;
}

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

const TXshCell &TXsheet::getCell(int row, int col) const
{
	static const TXshCell emptyCell;
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column)
		return emptyCell;
	TXshCellColumn *xshColumn = column->getCellColumn();
	if (!xshColumn)
		return emptyCell;
	return xshColumn->getCell(row);
}

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

bool TXsheet::setCell(int row, int col, const TXshCell &cell)
{
	if (row < 0 || col < 0)
		return false;

	bool wasColumnEmpty = isColumnEmpty(col);
	TXshCellColumn *cellColumn;

	if (!cell.isEmpty()) {
		TXshLevel *level = cell.m_level.getPointer();
		assert(level);

		int levelType = level->getType();
		TXshColumn::ColumnType type = TXshColumn::eLevelType;

		if (levelType == SND_XSHLEVEL)
			type = TXshColumn::eSoundType;
		else if (levelType == SND_TXT_XSHLEVEL)
			type = TXshColumn::eSoundTextType;
		else if (levelType == PLT_XSHLEVEL)
			type = TXshColumn::ePaletteType;
		else if (levelType == ZERARYFX_XSHLEVEL)
			type = TXshColumn::eZeraryFxType;
		else if (levelType == MESH_XSHLEVEL)
			type = TXshColumn::eMeshType;

		cellColumn = touchColumn(col, type)->getCellColumn();
	} else {
		TXshColumn *column = getColumn(col);
		cellColumn = column ? column->getCellColumn() : 0;
	}

	if (!cellColumn || cellColumn->isLocked())
		return false;

	cellColumn->setXsheet(this);

	if (!cellColumn->setCell(row, cell)) {
		if (wasColumnEmpty) {
			removeColumn(col);
			insertColumn(col);
		}

		return false;
	}

	TFx *fx = cellColumn->getFx();
	if (wasColumnEmpty && fx && fx->getOutputConnectionCount() == 0 && cellColumn->getPaletteColumn() == 0)
		getFxDag()->addToXsheet(fx);

	if (cell.isEmpty())
		updateFrameCount();
	else if (row >= m_imp->m_frameCount)
		m_imp->m_frameCount = row + 1;

	TNotifier::instance()->notify(TXsheetChange());

	return true;
}

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

void TXsheet::getCells(int row, int col, int rowCount, TXshCell cells[]) const
{
	static const TXshCell emptyCell;
	int i;
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column) {
		for (i = 0; i < rowCount; i++)
			cells[i] = emptyCell;
		return;
	}
	TXshCellColumn *xshColumn = column->getCellColumn();
	if (!xshColumn) {
		for (i = 0; i < rowCount; i++)
			cells[i] = emptyCell;
		return;
	}
	xshColumn->getCells(row, rowCount, cells);
}

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

bool TXsheet::setCells(int row, int col, int rowCount, const TXshCell cells[])
{
	static const TXshCell emptyCell;
	int i = 0;
	while (i < rowCount && cells[i].isEmpty())
		i++;

	// inserito da Elisa verso novembre 2009.
	// cosi' ha il difetto che se assegno celle vuote non fa nulla
	// per ora lo commento. bisogna indagare se questo rompe qualcosa

	// ho modificato il seguito per gestire il caso in cui i>=rowCount
	// => niente livelli dentro cells

	// if(i>=rowCount)
	//  return false;

	TXshColumn::ColumnType type = TXshColumn::eLevelType;
	if (i < rowCount) {
		TXshLevel *level = cells[i].m_level.getPointer();
		int levelType = level->getType();

		if (levelType == SND_XSHLEVEL)
			type = TXshColumn::eSoundType;
		else if (levelType == SND_TXT_XSHLEVEL)
			type = TXshColumn::eSoundTextType;
		else if (levelType == PLT_XSHLEVEL)
			type = TXshColumn::ePaletteType;
		else if (levelType == ZERARYFX_XSHLEVEL)
			type = TXshColumn::eZeraryFxType;
		else if (levelType == MESH_XSHLEVEL)
			type = TXshColumn::eMeshType;
	}
	bool wasColumnEmpty = isColumnEmpty(col);
	TXshCellColumn *column = touchColumn(col, type)->getCellColumn();
	if (!column)
		return false;

	int oldColRowCount = column->getMaxFrame() + 1;
	bool ret = column->setCells(row, rowCount, cells);
	if (!ret || column->isLocked()) {
		if (wasColumnEmpty) {
			removeColumn(col);
			insertColumn(col);
		}
		return false;
	}
	int newColRowCount = column->getMaxFrame() + 1;

	TFx *fx = column->getFx();
	if (wasColumnEmpty && fx && fx->getOutputConnectionCount() == 0)
		getFxDag()->addToXsheet(fx);
	column->setXsheet(this);

	if (newColRowCount > m_imp->m_frameCount)
		m_imp->m_frameCount = newColRowCount;
	else {
		if (oldColRowCount == m_imp->m_frameCount && newColRowCount < m_imp->m_frameCount)
			updateFrameCount();
	}

	return true;
}

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

void TXsheet::insertCells(int row, int col, int rowCount)
{
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column || column->isLocked())
		return;
	TXshCellColumn *xshColumn = column->getCellColumn();
	if (!xshColumn)
		return;
	xshColumn->insertEmptyCells(row, rowCount);
	//aggiorno il frame count
	int fc = xshColumn->getMaxFrame() + 1;
	if (fc > m_imp->m_frameCount)
		m_imp->m_frameCount = fc;
}

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

void TXsheet::removeCells(int row, int col, int rowCount)
{
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column || column->isLocked())
		return;

	TXshCellColumn *xshCellColumn = column->getCellColumn();
	if (!xshCellColumn)
		return;

	int oldColRowCount = xshCellColumn->getMaxFrame() + 1;
	xshCellColumn->removeCells(row, rowCount);

	//aggiornamento framecount
	if (oldColRowCount == m_imp->m_frameCount)
		updateFrameCount();

	TNotifier::instance()->notify(TXsheetChange());
}

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

void TXsheet::clearCells(int row, int col, int rowCount)
{
	const TXshColumnP &column = m_imp->m_columnSet.getColumn(col);
	if (!column || column->isLocked())
		return;

	TXshCellColumn *xshCellColumn = column->getCellColumn();
	if (!xshCellColumn)
		return;

	int oldColRowCount = xshCellColumn->getMaxFrame() + 1;
	xshCellColumn->clearCells(row, rowCount);

	//aggiornamento framecount
	if (oldColRowCount == m_imp->m_frameCount)
		updateFrameCount();
}

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

void TXsheet::clearAll()
{
	int c0 = 0, c1 = m_imp->m_columnSet.getColumnCount() - 1;
	int r0 = 0, r1 = getFrameCount() - 1;
	m_imp->m_columnSet.clear();

	if (m_imp->m_pegTree) {
		delete m_imp->m_pegTree;
		m_imp->m_pegTree = new TStageObjectTree();
		m_imp->m_pegTree->setHandleManager(m_imp->m_handleManager);
		m_imp->m_pegTree->createGrammar(this);
	}

	if (m_imp->m_fxDag) {
		delete m_imp->m_fxDag;
		m_imp->m_fxDag = new FxDag();
	}

	m_imp->m_frameCount = 0;
	m_imp->m_mixedSound = 0;
}

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

int TXsheet::getCellRange(int col, int &r0, int &r1) const
{
	r0 = 0;
	r1 = -1;
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column)
		return 0;
	TXshCellColumn *cellColumn = column->getCellColumn();
	if (!cellColumn)
		return 0;
	return cellColumn->getRange(r0, r1);
}

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

int TXsheet::getMaxFrame(int col) const
{
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	if (!column)
		return 0;
	return column->getMaxFrame();
}

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

bool TXsheet::isColumnEmpty(int col) const
{
	TXshColumnP column = m_imp->m_columnSet.getColumn(col);
	return column ? column->isEmpty() : true;
}

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

void TXsheet::getUsedLevels(set<TXshLevel *> &levels) const
{
	set<const TXsheet *> visitedXshs;
	vector<const TXsheet *> todoXshs;

	visitedXshs.insert(this);
	todoXshs.push_back(this);

	while (!todoXshs.empty()) {
		const TXsheet *xsh = todoXshs.back();
		todoXshs.pop_back();

		int c0 = 0, c1 = xsh->getColumnCount() - 1;
		for (int c = c0; c <= c1; ++c) {
			TXshColumnP column = const_cast<TXsheet *>(xsh)->getColumn(c);
			if (!column)
				continue;

			TXshCellColumn *cellColumn = column->getCellColumn();
			if (!cellColumn)
				continue;

			int r0, r1;
			if (!cellColumn->getRange(r0, r1))
				continue;

			TXshLevel *level = 0;
			for (int r = r0; r <= r1; r++) {
				TXshCell cell = cellColumn->getCell(r);
				if (cell.isEmpty() || !cell.m_level)
					continue;

				if (level != cell.m_level.getPointer()) {
					level = cell.m_level.getPointer();
					levels.insert(level);
					if (level->getChildLevel()) {
						TXsheet *childXsh = level->getChildLevel()->getXsheet();
						if (visitedXshs.count(childXsh) == 0) {
							visitedXshs.insert(childXsh);
							todoXshs.push_back(childXsh);
						}
					}
				}
			}
		}
	}
}

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

bool TXsheet::isLevelUsed(TXshLevel *level) const
{
	set<TXshLevel *> levels;
	getUsedLevels(levels);
	return levels.count(level) > 0;
}

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

TStageObject *TXsheet::getStageObject(const TStageObjectId &id) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id);
}

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

TStageObjectTree *TXsheet::getStageObjectTree() const
{
	return m_imp->m_pegTree;
}

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

TAffine TXsheet::getPlacement(const TStageObjectId &id, int frame) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getPlacement(frame);
}

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

double TXsheet::getZ(const TStageObjectId &id, int frame) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getZ(frame);
}

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

double TXsheet::getNoScaleZ(const TStageObjectId &id) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getNoScaleZ();
}

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

TAffine TXsheet::getParentPlacement(const TStageObjectId &id, int frame) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getParentPlacement(frame);
}

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

TPointD TXsheet::getCenter(const TStageObjectId &id, int frame) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getCenter(frame);
}

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

void TXsheet::setCenter(const TStageObjectId &id, int frame, const TPointD &center)
{
	assert(id != TStageObjectId::NoneId);
	m_imp->m_pegTree->getStageObject(id)->setCenter(frame, center);
}

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

TStageObjectId TXsheet::getStageObjectParent(const TStageObjectId &id)
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->getParent();
}

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

void TXsheet::setStageObjectParent(const TStageObjectId &id, const TStageObjectId &parentId)
{
	assert(id != TStageObjectId::NoneId);
	m_imp->m_pegTree->getStageObject(id)->setParent(parentId);
}

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

bool TXsheet::hasChildren(const TStageObjectId &id) const
{
	assert(id != TStageObjectId::NoneId);
	return m_imp->m_pegTree->getStageObject(id)->hasChildren();
}

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

TAffine TXsheet::getCameraAff(int frame) const
{
	TStageObjectId cameraId = getStageObjectTree()->getCurrentCameraId();
	TAffine cameraAff = getPlacement(cameraId, frame);
	double cameraZ = getZ(cameraId, frame);
	TAffine aff = cameraAff * TScale((1000 + cameraZ) / 1000);
	return aff;
}

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

void TXsheet::reverseCells(int r0, int c0, int r1, int c1)
{
	int rowCount = r1 - r0;
	if (rowCount < 0 || c1 - c0 < 0)
		return;

	for (int j = c0; j <= c1; j++) {
		int i1, i2;
		for (i1 = r0, i2 = r1; i1 < i2; i1++, i2--) {
			TXshCell app1 = getCell(i1, j);
			TXshCell app2 = getCell(i2, j);
			setCell(i1, j, app2);
			setCell(i2, j, app1);
		}
	}
}

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

void TXsheet::swingCells(int r0, int c0, int r1, int c1)
{
	int rowCount = r1 - r0;
	if (rowCount < 0 || c1 - c0 < 0)
		return;
	int r0Mod = r1 + 1;
	for (int c = c0; c <= c1; ++c)
		insertCells(r0Mod, c, rowCount);

	for (int j = c0; j <= c1; j++) {
		for (int i1 = r0Mod, i2 = r1 - 1; i2 >= r0; i1++, i2--) {
			TXshCell cell = getCell(i2, j);
			setCell(i1, j, cell);
		}
	}
}

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

bool TXsheet::incrementCells(int r0, int c0, int r1, int c1, vector<std::pair<TRect, TXshCell>> &forUndo)
{
	for (int j = c0; j <= c1; j++) {
		int i = r0;
		while (getCell(i, j).isEmpty() && i <= r1 - 1)
			i++;

		for (; i <= r1 - 1; i++) {
			if (getCell(i + 1, j).isEmpty())
				break;
			const TXshCell &ce1 = getCell(i, j), &ce2 = getCell(i + 1, j);
			if (ce2.getSimpleLevel() != ce1.getSimpleLevel() ||
				ce2.getFrameId().getNumber() < ce1.getFrameId().getNumber())
				return false;
		}
		i = r0;
		while (getCell(i, j).isEmpty() && i <= r1 - 1)
			i++;
		int count;
		for (; i <= r1 - 1; i++) {
			count = 1;
			if (getCell(i + 1, j).isEmpty())
				continue;

			int frame1 = getCell(i, j).getFrameId().getNumber();
			if (frame1 == -1)
				break;
			while (!getCell(i + 1, j).isEmpty() && getCell(i + 1, j).getFrameId().getNumber() == getCell(i, j).getFrameId().getNumber())
				i++, count++;

			int frame2 = getCell(i + 1, j).getFrameId().getNumber();
			if (frame2 == -1)
				break;

			if (frame1 + count == frame2)
				continue;
			else if (frame1 + count < frame2) //aggiungo
			{
				int numCells = frame2 - frame1 - count;
				insertCells(i + 1, j, numCells);
				forUndo.push_back(std::pair<TRect, TXshCell>(TRect(i + 1, j, i + 1 + numCells - 1, j), TXshCell()));
				for (int k = 1; k <= numCells; k++)
					setCell(i + k, j, getCell(i, j));
				i += numCells;
				r1 += numCells;
			} else //tolgo
			{
				int numCells = count - frame2 + frame1;
				i = i - numCells;
				forUndo.push_back(std::pair<TRect, TXshCell>(TRect(i + 1, j, i + 1 + numCells - 1, j), getCell(i + 1, j)));
				removeCells(i + 1, j, numCells);
				r1 -= numCells;
			}
		}
	}
	return true;
}

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

void TXsheet::duplicateCells(int r0, int c0, int r1, int c1, int upTo)
{
	assert(upTo >= r1 + 1);
	int chunk = r1 - r0 + 1;

	for (int j = c0; j <= c1; j++) {
		insertCells(r1 + 1, j, upTo - (r1 + 1) + 1);
		for (int i = r1 + 1; i <= upTo; i++)
			setCell(i, j, getCell(r0 + ((i - (r1 + 1)) % chunk), j));
	}
}

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

void TXsheet::stepCells(int r0, int c0, int r1, int c1, int type)
{
	int nr = r1 - r0 + 1;
	int nc = c1 - c0 + 1;
	if (nr < 1 || nc <= 0)
		return;
	int size = nr * nc;
	std::unique_ptr<TXshCell[]> cells(new TXshCell[size]);
	if (!cells)
		return;
	//salvo il contenuto delle celle in cells
	int k = 0;
	for (int r = r0; r <= r1; r++)
		for (int c = c0; c <= c1; c++) {
			cells[k++] = getCell(r, c);
		}

	int nrows = nr * (type - 1);

	for (int c = c0; c <= c1; ++c)
		insertCells(r1 + 1, c, nrows);

	for (int j = c0; j <= c1; j++) {
		int i, k;
		for (i = r0, k = j - c0; k < size; k += nc) {
			for (int i1 = 0; i1 < type; i1++) {
				if (cells[k].isEmpty())
					clearCells(i + i1, j);
				else
					setCell(i + i1, j, cells[k]);
			}
			i += type; //dipende dal tipo di step (2 o 3 per ora)
		}
	}
}

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

void TXsheet::increaseStepCells(int r0, int c0, int &r1, int c1)
{
	int c, size = r1 - r0 + 1;
	QList<int> ends;
	for (c = c0; c <= c1; c++) {
		int r = r0, i = 0, rEnd = r1;
		while (r <= rEnd) {
			TXshCell cell = getCell(r, c);
			if (!cell.isEmpty()) {
				insertCells(r, c);
				setCell(r, c, cell);
				rEnd++;
				r++;
				while (cell == getCell(r, c) && r <= rEnd)
					r++;
			} else
				r++;
			i++;
		}
		ends.append(rEnd);
	}
	if (ends.isEmpty())
		return;
	//controllo se devo cambiare la selezione
	bool allIncreaseIsEqual = true;
	for (c = 0; c < ends.size() - 1 && allIncreaseIsEqual; c++)
		allIncreaseIsEqual = allIncreaseIsEqual && ends[c] == ends[c + 1];
	if (allIncreaseIsEqual)
		r1 = ends[0];
}

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

void TXsheet::decreaseStepCells(int r0, int c0, int &r1, int c1)
{
	int c, size = r1 - r0 + 1;
	QList<int> ends;
	for (c = c0; c <= c1; c++) {
		int r = r0, i = 0, rEnd = r1;
		while (r <= rEnd) {
			TXshCell cell = getCell(r, c);
			if (!cell.isEmpty()) {
				r++;
				bool removed = false;
				while (cell == getCell(r, c) && r <= rEnd) {
					if (!removed) {
						removed = true;
						removeCells(r, c);
						rEnd--;
					} else
						r++;
				}
			} else
				r++;
			i++;
		}
		ends.append(rEnd);
	}
	if (ends.isEmpty())
		return;
	//controllo se devo cambiare la selezione
	bool allDecreaseIsEqual = true;
	for (c = 0; c < ends.size() - 1 && allDecreaseIsEqual; c++)
		allDecreaseIsEqual = allDecreaseIsEqual && ends[c] == ends[c + 1];
	if (allDecreaseIsEqual)
		r1 = ends[0];
}

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

void TXsheet::eachCells(int r0, int c0, int r1, int c1, int type)
{
	int nr = r1 - r0 + 1;
	int nc = c1 - c0 + 1;
	if (nr < type || nc <= 0)
		return;

	int newRows = nr % type ? nr / type + 1 : nr / type;

	int size = newRows * nc;
	assert(size > 0);
	std::unique_ptr<TXshCell[]> cells(new TXshCell[size]);
	assert(cells);

	int i, j, k;
	for (j = r0, i = 0; i < size; j += type) //in cells copio il contenuto delle celle che mi interessano
	{
		for (k = c0; k <= c1; k++, i++)
			cells[i] = getCell(j, k);
	}

	int c;
	for (c = c0; c <= c1; ++c)
		removeCells(r0 + newRows, c, nr - newRows);

	for (i = r0, k = 0; i < r0 + newRows && k < size; i++)
		for (j = c0; j <= c1; j++) {
			//----110523 iwasawa Eachでできた空きセルに、操作前のセルの中身が残ってしまう不具合を修正
			if (cells[k].isEmpty())
				clearCells(i, j);
			else
				setCell(i, j, cells[k]);
			k++;
		}
}

//-----------------------------------------------------------------------------
/*! force cells order in n-steps. returns the row amount after process
 */
int TXsheet::reframeCells(int r0, int r1, int col, int type)
{
	//Row amount in the selection
	int nr = r1 - r0 + 1;

	if (nr < 1)
		return 0;

	QVector<TXshCell> cells;

	cells.clear();

	for (int r = r0; r <= r1; r++) {
		if (cells.size() == 0 || cells.last() != getCell(r, col))
			cells.push_back(getCell(r, col));
	}

	if (cells.empty())
		return 0;

	// row amount after n-step
	int nrows = cells.size() * type;

	// if needed, insert cells
	if (nr < nrows) {
		insertCells(r1 + 1, col, nrows - nr);
	}
	// if needed, remove cells
	else if (nr > nrows) {
		removeCells(r0 + nrows, col, nr - nrows);
	}

	for (int i = r0, k = 0; i < r0 + nrows; k++) {
		for (int i1 = 0; i1 < type; i1++) {
			if (cells[k].isEmpty())
				clearCells(i + i1, col);
			else
				setCell(i + i1, col, cells[k]);
		}
		i += type; //dipende dal tipo di step (2 o 3 per ora)
	}

	return nrows; // return row amount after process
}

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

void TXsheet::resetStepCells(int r0, int c0, int r1, int c1)
{
	int c, size = r1 - r0 + 1;
	for (c = c0; c <= c1; c++) {
		int r = r0, i = 0;
		TXshCell *cells = new TXshCell[size];
		while (r <= r1) {
			//mi prendo le celle che mi servono
			cells[i] = getCell(r, c);
			r++;
			while (cells[i] == getCell(r, c) && r <= r1)
				r++;
			i++;
		}

		size = i;
		removeCells(r0, c, r1 - r0 + 1);
		insertCells(r0, c, i);
		i = 0;
		r = r0;
		for (i = 0; i < size; i++, r++)
			setCell(r, c, cells[i]);
	}
}

//-----------------------------------------------------------------------------
/*! Roll first cells of rect r0,c0,r1,c1. Move cells contained in first row to last row.
*/
void TXsheet::rollupCells(int r0, int c0, int r1, int c1)
{
	int nc = c1 - c0 + 1;
	int size = 1 * nc;
	assert(size > 0);
	std::unique_ptr<TXshCell[]> cells(new TXshCell[size]);
	assert(cells);

	//in cells copio il contenuto delle celle che mi interessano
	int k;
	for (k = c0; k <= c1; k++)
		cells[k - c0] = getCell(r0, k);

	for (k = c0; k <= c1; k++)
		removeCells(r0, k, 1);

	for (k = c0; k <= c1; k++) {
		insertCells(r1, k, 1);
		setCell(r1, k, cells[k - c0]); //setto le celle
	}
}

//-----------------------------------------------------------------------------
/*! Roll last cells of rect r0,c0,r1,c1. Move cells contained in last row to first row.
*/
void TXsheet::rolldownCells(int r0, int c0, int r1, int c1)
{
	int nc = c1 - c0 + 1;
	int size = 1 * nc;
	assert(size > 0);
	std::unique_ptr<TXshCell[]> cells(new TXshCell[size]);
	assert(cells);

	//in cells copio il contenuto delle celle che mi interessano
	int k;
	for (k = c0; k <= c1; k++)
		cells[k - c0] = getCell(r1, k);

	for (k = c0; k <= c1; k++)
		removeCells(r1, k, 1);

	for (k = c0; k <= c1; k++) {
		insertCells(r0, k, 1);
		setCell(r0, k, cells[k - c0]); //setto le celle
	}
}

//-----------------------------------------------------------------------------
/*! Stretch cells contained in rect r0,c0,r1,c1, from r1-r0+1 to nr.
		If nr>r1-r0+1 add cells, overwise remove cells. */
void TXsheet::timeStretch(int r0, int c0, int r1, int c1, int nr)
{
	int oldNr = r1 - r0 + 1;
	if (nr > oldNr) /* ingrandisce */
	{
		int c;
		for (c = c0; c <= c1; c++) {
			int dn = nr - oldNr;
			assert(oldNr > 0);
			std::unique_ptr<TXshCell[]> cells(new TXshCell[oldNr]);
			assert(cells);
			getCells(r0, c, oldNr, cells.get());
			insertCells(r0 + 1, c, dn);
			int i;
			for (i = nr - 1; i >= 0; i--) {
				int j = i * double(oldNr) / double(nr);
				if (j < i)
					setCell(i + r0, c, cells[j]);
			}
		}
	} else /* rimpicciolisce */
	{
		int c;
		for (c = c0; c <= c1; c++) {
			int dn = oldNr - nr;
			std::unique_ptr<TXshCell[]> cells(new TXshCell[oldNr]);
			assert(cells);
			getCells(r0, c, oldNr, cells.get());
			int i;
			for (i = 0; i < nr; i++) {
				int j = i * double(oldNr) / double(nr);
				if (j > i)
					setCell(i + r0, c, cells[j]);
			}
			removeCells(r1 - dn + 1, c, dn);
		}
	}
}

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

int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, bool overwrite)
{
	if (!xl)
		return 0;
	std::vector<TFrameId> fids;
	xl->getFids(fids);
	int frameCount = 1;
	if (fids.empty()) {
		setCell(row, col, TXshCell(xl, TFrameId(1)));
		updateFrameCount();
		return frameCount;
	}
	exposeLevel(row, col, xl, fids, overwrite);
	return (int)fids.size();
}

//-----------------------------------------------------------------------------
// customized version for load level popup
int TXsheet::exposeLevel(int row,
						 int col,
						 TXshLevel *xl,
						 std::vector<TFrameId> &fIds_,
						 int xFrom,
						 int xTo,
						 int step,
						 int inc,
						 int frameCount,
						 bool doesFileActuallyExist)
{
	if (!xl)
		return 0;
	std::vector<TFrameId> fids;

	if (doesFileActuallyExist)
		xl->getFids(fids);
	else {
		for (int i = 0; i < (int)fIds_.size(); i++) {
			fids.push_back(fIds_[i]);
		}
	}

	// multiple exposing
	if (frameCount < 0 || xFrom < 0 || xTo < 0 || step < 0 || inc < 0) {
		insertCells(row, col, xl->getFrameCount());

		frameCount = 1;
		if (fids.empty())
			setCell(row, col, TXshCell(xl, TFrameId(1)));
		else {
			frameCount = (int)fids.size();
			insertCells(row, col, frameCount);
			std::vector<TFrameId>::iterator it;
			for (it = fids.begin(); it != fids.end(); ++it)
				setCell(row++, col, TXshCell(xl, *it));
		}
		updateFrameCount();
		return frameCount;
	}

	//single exposing

	insertCells(row, col, frameCount);

	if (fids.empty()) {
		setCell(row, col, TXshCell(xl, TFrameId(1)));
	} else {
		if (inc == 0) //inc = Auto
		{
			std::vector<TFrameId>::iterator it;
			it = fids.begin();
			while (it->getNumber() < xFrom)
				it++;

			if (step == 0) //Step = Auto
			{
				std::vector<TFrameId>::iterator next_it;
				next_it = it;
				next_it++;

				int startFrame = it->getNumber();

				for (int f = startFrame; f < startFrame + frameCount; f++) {
					if (next_it != fids.end() && f >= next_it->getNumber()) {
						it++;
						next_it++;
					}
					setCell(row++, col, TXshCell(xl, *it));
				}
			}

			else //Step != Auto
			{
				int loopCount = frameCount / step;
				for (int loop = 0; loop < loopCount; loop++) {
					for (int s = 0; s < step; s++) {
						setCell(row++, col, TXshCell(xl, *it));
					}
					it++;
				}
			}

		} else //inc != Auto
		{
			int loopCount;
			if (step == 0) //Step = Auto
				step = inc;

			loopCount = frameCount / step;

			for (int loop = 0; loop < loopCount; loop++) {
				TFrameId id(xFrom + loop * inc, fids.begin()->getLetter());
				for (int s = 0; s < step; s++) {
					setCell(row++, col, TXshCell(xl, id));
				}
			}
		}
	}
	updateFrameCount();
	return frameCount;
}

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

void TXsheet::exposeLevel(int row, int col, TXshLevel *xl, std::vector<TFrameId> fids, bool overwrite)
{
	int frameCount = (int)fids.size();
	if (!overwrite)
		insertCells(row, col, frameCount);
	std::vector<TFrameId>::iterator it;
	for (it = fids.begin(); it != fids.end(); ++it)
		setCell(row++, col, TXshCell(xl, *it));
	updateFrameCount();
}

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

void TXsheet::updateFrameCount()
{
	m_imp->m_frameCount = 0;
	for (int i = 0; i < m_imp->m_columnSet.getColumnCount(); ++i) {
		TXshColumnP cc = m_imp->m_columnSet.getColumn(i);
		if (cc && !cc->isEmpty())
			m_imp->m_frameCount = tmax(m_imp->m_frameCount, cc->getMaxFrame() + 1);
	}
}

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

void TXsheet::loadData(TIStream &is)
{
	clearAll();
	TStageObjectId cameraId = TStageObjectId::CameraId(0);
	TStageObject *firstCamera = getStageObject(cameraId);
	m_imp->m_pegTree->removeStageObject(cameraId);

	int col = 0;
	string tagName;
	while (is.openChild(tagName)) {
		if (tagName == "columns") {
			while (!is.eos()) {
				TPersist *p = 0;
				is >> p;
				TXshColumn *column = dynamic_cast<TXshColumn *>(p);
				if (!column)
					throw TException("expected xsheet column");
				m_imp->m_columnSet.insertColumn(col++, column);
				column->setXsheet(this);
				if (TXshZeraryFxColumn *zc = dynamic_cast<TXshZeraryFxColumn *>(column)) {
					TFx *fx = zc->getZeraryColumnFx()->getZeraryFx();
					int fxTypeCount = m_imp->m_fxDag->getFxTypeCount(fx);
					int maxFxTypeId = tmax(fxTypeCount, fx->getAttributes()->getId());
					m_imp->m_fxDag->updateFxTypeTable(fx, maxFxTypeId);
					m_imp->m_fxDag->updateFxIdTable(fx);
					for (int j = 0; j < fx->getParams()->getParamCount(); j++) {
						TParam *param = fx->getParams()->getParam(j);
						if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(param))
							getStageObjectTree()->setGrammar(dp);
						else if (dynamic_cast<TPointParam *>(param) ||
								 dynamic_cast<TRangeParam *>(param) ||
								 dynamic_cast<TPixelParam *>(param)) {
							TParamSet *paramSet = dynamic_cast<TParamSet *>(param);
							assert(paramSet);
							int f;
							for (f = 0; f < paramSet->getParamCount(); f++) {
								TDoubleParam *dp = dynamic_cast<TDoubleParam *>(paramSet->getParam(f).getPointer());
								if (!dp)
									continue;
								getStageObjectTree()->setGrammar(dp);
							}
						}
					}
				}
			}
		} else if (tagName == "pegbars") {
			TPersist *p = m_imp->m_pegTree;
			is >> *p;
		} else if (tagName == "fxnodes") {
			m_imp->m_fxDag->loadData(is);
			std::vector<TFx *> fxs;
			m_imp->m_fxDag->getFxs(fxs);
			for (int i = 0; i < (int)fxs.size(); i++) {
				TFx *fx = fxs[i];
				for (int j = 0; j < fx->getParams()->getParamCount(); j++) {
					TParam *param = fx->getParams()->getParam(j);
					if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(param))
						getStageObjectTree()->setGrammar(dp);
					else if (dynamic_cast<TPointParam *>(param) ||
							 dynamic_cast<TRangeParam *>(param) ||
							 dynamic_cast<TPixelParam *>(param)) {
						TParamSet *paramSet = dynamic_cast<TParamSet *>(param);
						assert(paramSet);
						int f;
						for (f = 0; f < paramSet->getParamCount(); f++) {
							TDoubleParam *dp = dynamic_cast<TDoubleParam *>(paramSet->getParam(f).getPointer());
							if (!dp)
								continue;
							getStageObjectTree()->setGrammar(dp);
						}
					}
				}
			}

			if (is.matchEndTag())
				continue;

			// was ist dass?
			TFxSet fxSet;
			fxSet.loadData(is);
		} else if (tagName == "columnFan") {
			m_imp->m_columnFan.loadData(is);
		} else if (tagName == "noteSet") {
			m_notes->loadData(is);
		} else {
			throw TException("xsheet, unknown tag: " + tagName);
		}
		is.closeChild();
	}
	updateFrameCount();
}

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

void TXsheet::saveData(TOStream &os)
{
	os.openChild("columns");
	for (int c = 0; c < m_imp->m_columnSet.getColumnCount(); ++c) {
		TXshColumnP column = m_imp->m_columnSet.getColumn(c);
		if (column && c < getFirstFreeColumnIndex())
			os << column.getPointer();
	}
	os.closeChild();
	os.openChild("pegbars");
	m_imp->m_pegTree->saveData(os, getFirstFreeColumnIndex());
	//os << *(m_imp->m_pegTree);
	os.closeChild();

	FxDag *fxDag = getFxDag();
	os.openChild("fxnodes");
	fxDag->saveData(os, getFirstFreeColumnIndex());
	os.closeChild();

	ColumnFan *columnFan = getColumnFan();
	if (!columnFan->isEmpty()) {
		os.openChild("columnFan");
		columnFan->saveData(os);
		os.closeChild();
	}

	TXshNoteSet *notes = getNotes();
	if (notes->getCount() > 0) {
		os.openChild("noteSet");
		notes->saveData(os);
		os.closeChild();
	}
}

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

PERSIST_IDENTIFIER(TXsheet, "xsheet")

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

void TXsheet::insertColumn(int col, TXshColumn::ColumnType type)
{
	insertColumn(col, TXshColumn::createEmpty(type));
}

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

void TXsheet::insertColumn(int col, TXshColumn *column)
{
	column->setXsheet(this);
	m_imp->m_columnSet.insertColumn(col, column);
	m_imp->m_pegTree->insertColumn(col);
	if (column->getPaletteColumn() == 0) // palette column are not connected to the xsheet fx node
	{
		TFx *fx = column->getFx();
		if (fx)
			getFxDag()->addToXsheet(fx);
	}
}

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

void TXsheet::removeColumn(int col)
{
	TXshColumn *column = getColumn(col);
	if (column) {
		TFx *fx = column->getFx();
		if (fx) {
			getFxDag()->removeFromXsheet(fx);

			// disconnetto dal columnFx tutti gli effetti connessi in uscita
			TFxPort *outPort = 0;
			while ((outPort = fx->getOutputConnection(0)))
				outPort->setFx(0);
		}
	}
	m_imp->m_columnSet.removeColumn(col);
	m_imp->m_pegTree->removeColumn(col);
}

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

void TXsheet::moveColumn(int srcIndex, int dstIndex)
{
	if (srcIndex == dstIndex)
		return;
	assert(srcIndex >= 0);
	assert(dstIndex >= 0);
	int col = tmax(srcIndex, dstIndex);
	if (col >= m_imp->m_columnSet.getColumnCount()) {
		int n = m_imp->m_columnSet.getColumnCount();
		touchColumn(col, TXshColumn::eLevelType);
		while (n <= col) {
			TXshColumn *column = getColumn(n);
			assert(column);
			column->setXsheet(this);
			n++;
		}
	}
	assert(m_imp->m_columnSet.getColumnCount() > srcIndex);
	assert(m_imp->m_columnSet.getColumnCount() > dstIndex);

	if (srcIndex < dstIndex) {
		int c0 = srcIndex;
		int c1 = dstIndex;
		assert(c0 < c1);
		m_imp->m_columnSet.rollLeft(c0, c1 - c0 + 1);
		for (int c = c0; c < c1; ++c)
			m_imp->m_pegTree->swapColumns(c, c + 1);
	} else {
		int c0 = dstIndex;
		int c1 = srcIndex;
		assert(c0 < c1);
		m_imp->m_columnSet.rollRight(c0, c1 - c0 + 1);
		for (int c = c1 - 1; c >= c0; --c)
			m_imp->m_pegTree->swapColumns(c, c + 1);
	}
}

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

TXshColumn *TXsheet::getColumn(int col) const
{
	return m_imp->m_columnSet.getColumn(col).getPointer();
}

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

int TXsheet::getColumnCount() const
{
	return m_imp->m_columnSet.getColumnCount();
}

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

int TXsheet::getFirstFreeColumnIndex() const
{
	int i = getColumnCount();
	while (i > 0 && isColumnEmpty(i - 1))
		--i;
	return i;
}

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

TXshColumn *TXsheet::touchColumn(int index, TXshColumn::ColumnType type)
{
	TXshColumn *column = m_imp->m_columnSet.touchColumn(index, type).getPointer();
	if (!column)
		return 0;

	// NOTE (Daniele): The following && should be a bug... but I fear I'd break something changing it.
	// Observe that the implied behavior is that of REPLACING AN EXISTING LEGITIMATE COLUMN!
	// Please, Inquire further if you're not upon release!

	if (column->isEmpty() && column->getColumnType() != type) {
		removeColumn(index);
		insertColumn(index, type);
		column = getColumn(index);
	}

	return column;
}

//=============================================================================
namespace
{ // Utility function
//-----------------------------------------------------------------------------

void searchAudioColumn(TXsheet *xsh, std::vector<TXshSoundColumn *> &sounds, bool isPreview = true)
{
	int i = 0;
	int columns = xsh->getColumnCount();

	for (; i < columns; ++i) {
		TXshColumn *column = xsh->getColumn(i);
		if (column) {
			TXshSoundColumn *soundCol = column->getSoundColumn();
			if (soundCol && ((isPreview && soundCol->isPreviewVisible()) || (!isPreview && soundCol->isCamstandVisible()))) {
				sounds.push_back(soundCol);
				continue;
			}
		}
	}
}
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------

TSoundTrack *TXsheet::makeSound(SoundProperties *properties)
{
	std::vector<TXshSoundColumn *> sounds;
	searchAudioColumn(this, sounds, properties->m_isPreview);
	if (!m_imp->m_mixedSound || *properties != *m_soundProperties) {
		if (!sounds.empty() && properties->m_fromFrame <= properties->m_toFrame)
			m_imp->m_mixedSound = sounds[0]->mixingTogether(sounds, properties->m_fromFrame,
															properties->m_toFrame, properties->m_frameRate);
		else
			m_imp->m_mixedSound = 0;
		delete m_soundProperties;
		m_soundProperties = properties;
	} else
		delete properties;
	return m_imp->m_mixedSound.getPointer();
}

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

void TXsheet::scrub(int frame, bool isPreview)
{
	double fps = getScene()->getProperties()->getOutputProperties()->getFrameRate();

	TXsheet::SoundProperties *prop = new TXsheet::SoundProperties();
	prop->m_isPreview = isPreview;

	TSoundTrack *st = makeSound(prop); // Absorbs prop's ownership
	if (!st)
		return;

	double samplePerFrame = st->getSampleRate() / fps;

	double s0 = frame * samplePerFrame,
		   s1 = s0 + samplePerFrame;

	play(st, s0, s1, false);
}

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

void TXsheet::stopScrub()
{
	if (m_player)
		m_player->stop();
}

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

void TXsheet::play(TSoundTrackP soundtrack, int s0, int s1, bool loop)
{
	if (!TSoundOutputDevice::installed())
		return;

	if (!m_player)
		m_player = new TSoundOutputDevice();

	if (m_player) {
		try {
			m_player->play(soundtrack, s0, s1, loop);
		} catch (TSoundDeviceException &) {
		}
	}
}

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

FxDag *TXsheet::getFxDag() const
{
	return m_imp->m_fxDag;
}

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

ColumnFan *TXsheet::getColumnFan() const
{
	return &m_imp->m_columnFan;
}

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

ToonzScene *TXsheet::getScene() const
{
	return m_imp->m_scene;
}

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

void TXsheet::setScene(ToonzScene *scene)
{
	m_imp->m_scene = scene;
}

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

bool TXsheet::checkCircularReferences(const TXshCell &cellCandidate)
{
	if (cellCandidate.isEmpty() || !cellCandidate.m_level->getChildLevel())
		return false;
	TXsheet *childCandidate = cellCandidate.m_level->getChildLevel()->getXsheet();
	return checkCircularReferences(childCandidate);
}

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

bool TXsheet::checkCircularReferences(TXshColumn *columnCandidate)
{
	if (!columnCandidate || !columnCandidate->getLevelColumn())
		return false;
	TXshLevelColumn *lc = columnCandidate->getLevelColumn();
	int r0 = 0, r1 = -1;
	if (lc->getRange(r0, r1) <= 0)
		return false;
	int r;
	TXshCell oldCell;
	for (r = r0; r <= r1; r++) {
		TXshCell cell = lc->getCell(r);
		// to speed up:
		if (cell.m_level.getPointer() == oldCell.m_level.getPointer())
			continue;
		if (checkCircularReferences(cell))
			return true;
		oldCell = cell;
	}
	return false;
}

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

void TXsheet::invalidateSound()
{
	m_imp->m_mixedSound = TSoundTrackP();
}

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

bool TXsheet::checkCircularReferences(TXsheet *childCandidate)
{
	if (this == childCandidate)
		return true;
	if (childCandidate == 0)
		return false;
	int i;
	for (i = 0; i < childCandidate->getColumnCount(); i++)
		if (checkCircularReferences(childCandidate->getColumn(i)))
			return true;
	return false;
}

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

// Builds the camstand bbox associated to the specified xsheet
TRectD TXsheet::getBBox(int r) const
{
	static const double maxDouble = (std::numeric_limits<double>::max)();
	static const TRectD voidRect(maxDouble, maxDouble, -maxDouble, -maxDouble);

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

	struct locals {
		static TRectD getBBox(const TXsheet *xsh, int r, int c)
		{
			// Discriminate cell content
			const TXshCell &cell = xsh->getCell(r, c);
			if (cell.isEmpty())
				return voidRect;

			if (TXshChildLevel *cl = cell.getChildLevel())
				return cl->getXsheet()->getBBox(cell.getFrameId().getNumber() - 1);

			TXshSimpleLevel *sl = cell.getSimpleLevel();
			if (!sl || !(sl->getType() & LEVELCOLUMN_XSHLEVEL)) // Avoid other mesh levels - which could
				return voidRect;								// be deformed too...

			// Retrieve column affine
			TAffine columnZaff;
			{
				TStageObject *colObj = xsh->getStageObject(TStageObjectId::ColumnId(c));

				const TAffine &columnAff = colObj->getPlacement(r); // ...
				double columnZ = colObj->getZ(r);					// ...
				double columnNoScaleZ = colObj->getGlobalNoScaleZ();

				TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
				TStageObject *camera = xsh->getStageObject(cameraId);

				const TAffine &cameraAff = camera->getPlacement(r); // ...
				double cameraZ = camera->getZ(r);					// ...

				if (!TStageObject::perspective(
						columnZaff,
						cameraAff, cameraZ,
						columnAff, columnZ,
						columnNoScaleZ))
					return voidRect;
			}

			const TRectD &bbox = sl->getBBox(cell.getFrameId());
			if (bbox.getLx() <= 0.0 || bbox.getLy() <= 0.0)
				return voidRect;

			return columnZaff * TScale(Stage::inch, Stage::inch) * bbox;
		}
	};

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

	// Initialize a union-neutral rect
	TRectD bbox(voidRect);

	// Traverse the xsheet's columns, adding the bbox of each
	int c, cCount = getColumnCount();
	for (c = 0; c != cCount; ++c) {
		// Skip empty or invisible columns
		TXshColumn *column = getColumn(c);
		if (column->isEmpty() || !column->isCamstandVisible())
			continue;

		const TRectD &colBBox = locals::getBBox(this, r, c);

		// Make the union
		bbox.x0 = tmin(bbox.x0, colBBox.x0);
		bbox.y0 = tmin(bbox.y0, colBBox.y0);
		bbox.x1 = tmax(bbox.x1, colBBox.x1);
		bbox.y1 = tmax(bbox.y1, colBBox.y1);
	}

	return bbox;
}