Blob Blame Raw


#include "tools/tool.h"

// TnzTools includes
#include "tools/toolcommandids.h"
#include "tools/toolhandle.h"
#include "tools/cursors.h"
#include "tools/tooloptions.h"

// TnzQt includes
#include "toonzqt/icongenerator.h"

// TnzLib includes
#include "toonzqt/menubarcommand.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshleveltypes.h"
#include "toonz/levelproperties.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"
#include "toonz/preferences.h"
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/tpalettehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/txshcell.h"
#include "toonz/tstageobject.h"
#include "toonz/tstageobjectspline.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/dpiscale.h"
#include "toonz/palettecontroller.h"

// TnzCore includes
#include "tvectorimage.h"
#include "timagecache.h"
#include "tstroke.h"
#include "tcolorstyles.h"
#include "ttoonzimage.h"
#include "trasterimage.h"

//*****************************************************************************************
//    Local namespace
//*****************************************************************************************

namespace
{

// Global variables

typedef std::pair<std::string, TTool::ToolTargetType> ToolKey;
typedef std::map<ToolKey, TTool *> ToolTable;
ToolTable *toolTable = 0;

std::set<std::string> *toolNames = 0;

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

// Local classes

struct DummyTool : public TTool {
	ToolType getToolType() const { return TTool::LevelReadTool; }	// Test level type
	ToolTargetType getTargetType() const { return TTool::NoTarget; } // Works on nothing
	int getCursorId() const { return ToolCursor::ForbiddenCursor; }  // Forbids everything

	DummyTool() : TTool("T_Dummy") {}

} theDummyTool;

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

class ToolSelector
{
	std::string m_toolName;

public:
	ToolSelector(std::string toolName) : m_toolName(toolName) {}

	void selectTool()
	{
		TTool::Application *app = TTool::getApplication();
		if (app)
			app->getCurrentTool()->setTool(QString::fromStdString(m_toolName));
	}
};

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

// Local functions

TFrameId getNewFrameId(TXshSimpleLevel *sl, int row)
{

	TFrameId fid(row + 1);
	if (sl->isFid(fid)) {
		fid = TFrameId(fid.getNumber(), 'a');
		while (fid.getLetter() < 'z' && sl->isFid(fid))
			fid = TFrameId(fid.getNumber(), fid.getLetter() + 1);
	}
	return fid;
}

} // namespace

//*****************************************************************************************
//    TTool  static members
//*****************************************************************************************

TTool::Application *TTool::m_application = 0;
std::set<TFrameId> TTool::m_selectedFrames = std::set<TFrameId>();
bool TTool::m_isLevelCreated = false;
bool TTool::m_isFrameCreated = false;

// m_cellsData
// brutto brutto. fix quick & dirty del baco #6213 (undo con animation sheet)
// bisogna ripensare la logica degli undo e del touchImage
// m_cellsData viene inizializzato nel touchImage() in modalita' animation sheet
// contiene una o due terne che rappresentano range di celle (dell'xsheet) modificate dall'inserimento
// di un nuovo frame: [r0,r1,type].
// type = 0 : vecchio (cella[r0-1]) => nuovo
// type = 1 : vuoto => vecchio (cella[r0-1])
// type = 2 : vuoto => nuovo
// cfr. il codice di TTool::touchImage() ToolUtils::TToolUndo::removeLevelAndFrameIfNeeded()

std::vector<int> TTool::m_cellsData;

//*****************************************************************************************
//    TTool  implementation
//*****************************************************************************************

TTool::TTool(string name)
	: m_name(name), m_viewer(0), m_targetType(NoTarget), m_enabled(true), m_active(false), m_picking(false)
{
}

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

TTool *TTool::getTool(string toolName, ToolTargetType targetType)
{
	if (!toolTable)
		return 0;
	ToolTable::iterator it = toolTable->find(std::make_pair(toolName, targetType));
	if (it == toolTable->end())
		return 0;
	return it->second;
}

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

void TTool::bind(int targetType)
{
	m_targetType = targetType;

	if (!toolTable)
		toolTable = new ToolTable();

	if (!toolNames)
		toolNames = new std::set<std::string>();

	std::string name = getName();
	if (toolNames->count(name) == 0) {
		toolNames->insert(name);

		// Initialize with the dummy tool
		toolTable->insert(std::make_pair(std::make_pair(name, ToonzImage), &theDummyTool));
		toolTable->insert(std::make_pair(std::make_pair(name, VectorImage), &theDummyTool));
		toolTable->insert(std::make_pair(std::make_pair(name, RasterImage), &theDummyTool));
		toolTable->insert(std::make_pair(std::make_pair(name, MeshImage), &theDummyTool));

		ToolSelector *toolSelector = new ToolSelector(name);
		CommandManager::instance()->setHandler(name.c_str(),
											   new CommandHandlerHelper<ToolSelector>(toolSelector, &ToolSelector::selectTool));
	}

	if (targetType & ToonzImage)
		(*toolTable)[std::make_pair(name, ToonzImage)] = this;
	if (targetType & VectorImage)
		(*toolTable)[std::make_pair(name, VectorImage)] = this;
	if (targetType & RasterImage)
		(*toolTable)[std::make_pair(name, RasterImage)] = this;
	if (targetType & MeshImage)
		(*toolTable)[std::make_pair(name, MeshImage)] = this;
}

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

ToolOptionsBox *TTool::createOptionsBox()
{
	TPaletteHandle *currPalette = m_application->getPaletteController()->getCurrentLevelPalette();
	ToolHandle *currTool = m_application->getCurrentTool();
	return new GenericToolOptionsBox(0, this, currPalette, 0, currTool);
}

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

double TTool::getPixelSize() const
{
	return m_viewer ? m_viewer->getPixelSize() : 1.0;
}

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

TXshCell TTool::getImageCell()
{
	assert(m_application);

	TXshCell result;

	TFrameHandle *currentFrame = m_application->getCurrentFrame();
	TXshLevelHandle *currentLevel = m_application->getCurrentLevel();

	if (currentFrame->isEditingLevel()) {
		if (TXshLevel *xl = currentLevel->getLevel()) {
			if (TXshSimpleLevel *sl = xl->getSimpleLevel()) {
				result.m_level = xl;
				result.m_frameId = currentFrame->getFid();
			}
		}
	} else {
		if (TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet()) {
			if (!m_application->getCurrentObject()->isSpline()) {
				int row = currentFrame->getFrame();
				int col = m_application->getCurrentColumn()->getColumnIndex();

				result = xsh->getCell(row, col);
			}
		}
	}

	return result;
}

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

TImage *TTool::getImage(bool toBeModified, int subsampling)
{
	assert(m_application);

	if (m_application->getCurrentFrame()->isPlaying())
		toBeModified = false; // In playback mode, you are not going to modify images
							  // Probably useless - tools are disabled when playing...
	const TXshCell &cell = getImageCell();
	if (cell.isEmpty()) {
		TObjectHandle *currentObject = m_application->getCurrentObject();
		return currentObject->isSpline() ? currentObject->getSplineImage() : (TImage *)0;
	} else
		return cell.getImage(toBeModified, subsampling).getPointer();
}

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

TImage *TTool::touchImage()
{
	if (!m_application)
		return 0;

	m_cellsData.clear();

	m_isLevelCreated = false;
	m_isFrameCreated = false;
	Preferences *pref = Preferences::instance();

	bool isAutoCreateEnabled = pref->isAutoCreateEnabled();
	bool animationSheetEnabled = pref->isAnimationSheetEnabled();

	TFrameHandle *currentFrame = m_application->getCurrentFrame();
	TXshLevelHandle *currentLevel = m_application->getCurrentLevel();

	if (currentFrame->isEditingLevel()) {
		// Editing level

		// no level => return 0
		TXshLevel *xl = currentLevel->getLevel();
		if (!xl)
			return 0;
		TXshSimpleLevel *sl = xl->getSimpleLevel();
		if (!sl || sl->isEmpty())
			return 0;

		TFrameId fid = currentFrame->getFid();
		TImageP img = sl->getFrame(fid, true);
		if (!img) {
			// no drawing found
			if (sl->isSubsequence() || sl->isReadOnly() || !isAutoCreateEnabled)
				return 0;

			// create a new drawing
			img = sl->createEmptyFrame();
			sl->setFrame(fid, img);
			currentLevel->notifyLevelChange();
			m_isFrameCreated = true;
		}
		return img.getPointer();
	} else {
		// editing xsheet
		if (m_application->getCurrentObject()->isSpline())
			return 0;

		TSceneHandle *currentScene = m_application->getCurrentScene();
		ToonzScene *scene = currentScene->getScene();
		int row = currentFrame->getFrame();
		int col = m_application->getCurrentColumn()->getColumnIndex();
		if (col < 0)
			return 0;

		TXsheetHandle *currentXsheet = m_application->getCurrentXsheet();
		TXsheet *xsh = currentXsheet->getXsheet();
		if (!xsh)
			return 0;

		TXshCell cell = xsh->getCell(row, col);
		TXshSimpleLevel *sl = cell.getSimpleLevel();

		if (sl != 0) {
			// current cell is not empty
			if (isAutoCreateEnabled && animationSheetEnabled && row > 0 &&
				xsh->getCell(row - 1, col) == xsh->getCell(row, col)) {
				// animationSheet is enabled and the current cell is a "hold". We must create a new drawing.
				// measure the hold length (starting from the current row) : r0-r1
				int r0 = row, r1 = row;
				while (xsh->getCell(r1 + 1, col) == cell)
					r1++;
				// find the proper frameid (possibly addisng suffix, in order to avoid a fid already used)
				TFrameId fid = getNewFrameId(sl, row);
				// create the new drawing
				TImageP img = sl->createEmptyFrame();
				m_isFrameCreated = true;
				// insert the drawing in the level
				sl->setFrame(fid, img);
				// update the cell
				cell = TXshCell(sl, fid);
				// update the xsheet (change the current cell and possibly all the following "hold")
				for (int r = r0; r <= r1; r++)
					xsh->setCell(r, col, cell);
				// notify
				currentXsheet->notifyXsheetChanged();
				currentScene->notifyCastChange();
				currentLevel->notifyLevelChange();
				m_cellsData.push_back(r0);
				m_cellsData.push_back(r1);
				m_cellsData.push_back(0);
			}
			// we've found the image. return it.
			return cell.getImage(true).getPointer();
		}

		// current cell is empty.
		if (!isAutoCreateEnabled)
			return 0;

		// get the column range
		int r0, r1;
		xsh->getCellRange(col, r0, r1);

		if (animationSheetEnabled && r0 <= r1) {
			// animation sheet enabled and not empty column. We must create a new drawing in the column level and possibly add "holds"

			// find the last not-empty cell before the current one (a) and the first after (b)
			int a = row - 1, b = row + 1;
			while (a >= r0 && xsh->getCell(a, col).isEmpty())
				a--;
			while (b <= r1 && xsh->getCell(b, col).isEmpty())
				b++;

			// find the level we must attach to
			if (a >= r0) {
				// there is a not-emtpy cell before the current one
				sl = xsh->getCell(a, col).getSimpleLevel();
			} else if (b <= r1) {
				sl = xsh->getCell(b, col).getSimpleLevel();
			}
			if (sl) {
				// note: sl should be always !=0 (the column is not empty)
				// if - for some reason - it is ==0 then we skip to the standard (i.e. !animationSheetEnabled) beahviour

				// create the drawing
				// find the proper frameid (possibly addisng suffix, in order to avoid a fid already used)
				TFrameId fid = getNewFrameId(sl, row);
				// create the new drawing
				TImageP img = sl->createEmptyFrame();
				m_isFrameCreated = true;
				// insert the drawing in the level
				sl->setFrame(fid, img);
				// update the cell
				cell = TXshCell(sl, fid);
				xsh->setCell(row, col, cell);

				// create holds
				if (a >= r0) {
					// create a hold before : [a+1, row-1]
					TXshCell aCell = xsh->getCell(a, col);
					for (int i = a + 1; i < row; i++)
						xsh->setCell(i, col, aCell);
					m_cellsData.push_back(a + 1);
					m_cellsData.push_back(row - 1);
					m_cellsData.push_back(1); // vuoto => vecchio

					if (b <= r1 && xsh->getCell(b, col).getSimpleLevel() == sl) {
						// create also a hold after
						for (int i = row + 1; i < b; i++)
							xsh->setCell(i, col, cell);
						m_cellsData.push_back(row);
						m_cellsData.push_back(b - 1);
						m_cellsData.push_back(2); // vuoto => nuovo
					} else {
						m_cellsData.push_back(row);
						m_cellsData.push_back(row);
						m_cellsData.push_back(2); // vuoto => nuovo
					}
				} else if (b <= r1) {
					// create a hold after
					for (int i = row + 1; i < b; i++)
						xsh->setCell(i, col, cell);
					m_cellsData.push_back(row);
					m_cellsData.push_back(b - 1);
					m_cellsData.push_back(2); // vuoto => nuovo
				}
			}
			// notify & return
			currentXsheet->notifyXsheetChanged();
			currentScene->notifyCastChange();
			currentLevel->notifyLevelChange();
			return cell.getImage(true).getPointer();
		}

		if (row > 0 && xsh->getCell(row - 1, col).getSimpleLevel() != 0 && !animationSheetEnabled) {
			sl = xsh->getCell(row - 1, col).getSimpleLevel();
			if (sl->getType() != OVL_XSHLEVEL || sl->getPath().getFrame() != TFrameId::NO_FRAME) {
				// la cella precedente contiene un drawing di un livello. animationSheet e' disabilitato
				// creo un nuovo frame
				currentLevel->setLevel(sl);
				if (sl->isSubsequence() || sl->isReadOnly())
					return 0;
				TFrameId fid = sl->index2fid(sl->getFrameCount());
				TImageP img = sl->createEmptyFrame();
				m_isFrameCreated = true;
				sl->setFrame(fid, img);
				cell = TXshCell(sl, fid);
				xsh->setCell(row, col, cell);
				currentXsheet->notifyXsheetChanged();
				currentScene->notifyCastChange();
				currentLevel->notifyLevelChange();
				return img.getPointer();
			}
		}

		// animation sheet disabled or empty column. autoCreate is enabled: we must create a new level
		int levelType = pref->getDefLevelType();
		TXshLevel *xl = scene->createNewLevel(levelType);
		sl = xl->getSimpleLevel();
		m_isLevelCreated = true;

		// create the drawing
		TFrameId fid = animationSheetEnabled ? getNewFrameId(sl, row) : TFrameId(1);
		TImageP img = sl->createEmptyFrame();
		m_isFrameCreated = true;
		sl->setFrame(fid, img);
		cell = TXshCell(sl, fid);
		xsh->setCell(row, col, cell);
		if (animationSheetEnabled) {
			m_cellsData.push_back(row);
			m_cellsData.push_back(row);
			m_cellsData.push_back(2); // vuoto => nuovo
		}

		currentXsheet->notifyXsheetChanged();
		currentScene->notifyCastChange();
		currentLevel->notifyLevelChange();
		return img.getPointer();
	}
}

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

void TTool::updateToolsPropertiesTranslation()
{
	ToolTable::iterator tt, tEnd(toolTable->end());
	for (tt = toolTable->begin(); tt != tEnd; ++tt)
		tt->second->updateTranslation();
}

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

void TTool::invalidate(const TRectD &rect)
{
	if (m_viewer) {
		if (rect.isEmpty())
			m_viewer->GLInvalidateAll();
		else {
			TPointD dpiScale(1, 1);
			TXshSimpleLevel *sl = getApplication()->getCurrentLevel()->getSimpleLevel();
			if (sl)
				dpiScale = getCurrentDpiScale(sl, getCurrentFid());
			m_viewer->GLInvalidateRect(getCurrentColumnMatrix() * TScale(dpiScale.x, dpiScale.y) * rect);
		}
	}
}

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

int TTool::pick(const TPoint &p)
{
	if (!m_viewer)
		return 0;

	m_picking = true;
	int ret = m_viewer->pick(p);
	m_picking = false;

	return ret;
}

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

TXsheet *TTool::getXsheet() const
{
	if (!m_application)
		return 0;
	return m_application->getCurrentXsheet()->getXsheet();
}

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

int TTool::getFrame()
{
	if (!m_application)
		return 0;
	return m_application->getCurrentFrame()->getFrame();
}

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

int TTool::getColumnIndex()
{
	if (!m_application)
		return 0;
	return m_application->getCurrentColumn()->getColumnIndex();
}

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

TStageObjectId TTool::getObjectId()
{
	if (!m_application)
		return TStageObjectId();
	return m_application->getCurrentObject()->getObjectId();
}

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

TTool::Application *TTool::getApplication()
{
	if (m_application == 0)
		assert(!"you MUST call the TTool::setApplication function in the main of the program!");
	return m_application;
}

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

/*! Notify change of current image: update icon and notify level change.
    If current object is a spline commit spline chenged.
    If current mode is EditingLevel touch current frame.
*/
void TTool::notifyImageChanged()
{
	onImageChanged();

	if (!m_application)
		return;

	m_application->getCurrentScene()->setDirtyFlag(true);
	if (m_application->getCurrentFrame()->isEditingLevel()) {
		TXshLevel *xl = m_application->getCurrentLevel()->getLevel();
		if (!xl)
			return;
		TXshSimpleLevel *sl = xl->getSimpleLevel();
		if (!sl)
			return;
		TFrameId fid = m_application->getCurrentFrame()->getFid();
		sl->touchFrame(fid);
		// sl->setDirtyFlag(true);
		IconGenerator::instance()->invalidate(sl, fid);
		IconGenerator::instance()->invalidateSceneIcon();
	} else {
		TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();
		if (!xsh)
			return;

		TObjectHandle *currentObject = m_application->getCurrentObject();

		if (currentObject->isSpline()) {
			m_application->getCurrentObject()->commitSplineChanges();
			TStageObject *pegbar = xsh->getStageObject(currentObject->getObjectId());
			IconGenerator::instance()->invalidate(pegbar->getSpline());
		} else {
			int row = m_application->getCurrentFrame()->getFrame();
			int col = m_application->getCurrentColumn()->getColumnIndex();
			if (col < 0)
				return;
			TXshCell cell = xsh->getCell(row, col);
			TXshSimpleLevel *sl = cell.getSimpleLevel();
			if (sl) {
				IconGenerator::instance()->invalidate(sl, cell.m_frameId);
				sl->touchFrame(cell.m_frameId);
				IconGenerator::instance()->invalidateSceneIcon();
			}
		}
	}
	m_application->getCurrentLevel()->notifyLevelChange();
}

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

/*! Notify change of image in \b fid: update icon and notify level change.
*/
void TTool::notifyImageChanged(const TFrameId &fid)
{
	onImageChanged();

	if (!m_application)
		return;

	m_application->getCurrentScene()->setDirtyFlag(true);
	if (m_application->getCurrentFrame()->isEditingLevel()) {
		TXshLevel *xl = m_application->getCurrentLevel()->getLevel();
		if (!xl)
			return;
		TXshSimpleLevel *sl = xl->getSimpleLevel();
		if (!sl)
			return;
		sl->setDirtyFlag(true);
		IconGenerator::instance()->invalidate(sl, fid);
		IconGenerator::instance()->invalidateSceneIcon();
	} else {
		int row = m_application->getCurrentFrame()->getFrame();
		int col = m_application->getCurrentColumn()->getColumnIndex();
		if (col < 0)
			return;
		TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();
		if (!xsh)
			return;
		TXshCell cell = xsh->getCell(row, col);
		TXshSimpleLevel *sl = cell.getSimpleLevel();
		if (sl) {
			IconGenerator::instance()->invalidate(sl, fid);
			IconGenerator::instance()->invalidateSceneIcon();
			sl->setDirtyFlag(true);
		}
	}
	m_application->getCurrentLevel()->notifyLevelChange();
}

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

TFrameId TTool::getCurrentFid() const
{
	if (!m_application)
		return TFrameId();

	TFrameHandle *fh = m_application->getCurrentFrame();

	if (fh->isEditingLevel())
		return fh->getFid();

	int row = m_application->getCurrentFrame()->getFrame();
	int col = m_application->getCurrentColumn()->getColumnIndex();
	TXshCell cell = m_application->getCurrentXsheet()
						->getXsheet()
						->getCell(row, col);
	if (cell.isEmpty())
		return TFrameId::NO_FRAME;

	return cell.getFrameId();
}

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

TAffine TTool::getCurrentColumnMatrix() const
{
	return getColumnMatrix(m_application->getCurrentColumn()->getColumnIndex());
}

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

TAffine TTool::getCurrentColumnParentMatrix() const
{
	if (!m_application)
		return TAffine();

	TFrameHandle *fh = m_application->getCurrentFrame();
	if (fh->isEditingLevel())
		return TAffine();
	int frame = fh->getFrame();
	int columnIndex = m_application->getCurrentColumn()->getColumnIndex();
	TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();
	TStageObjectId parentId = xsh->getStageObjectParent(TStageObjectId::ColumnId(columnIndex));
	return xsh->getPlacement(parentId, frame);
}

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

TAffine TTool::getCurrentObjectParentMatrix() const
{
	if (!m_application)
		return TAffine();

	TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();
	int frame = m_application->getCurrentFrame()->getFrame();
	TStageObjectId currentObjectId = m_application->getCurrentObject()->getObjectId();
	if (currentObjectId == TStageObjectId::NoneId)
		return TAffine();
	TStageObjectId parentId = xsh->getStageObjectParent(currentObjectId);
	if (parentId == TStageObjectId::NoneId)
		return TAffine();
	else
		return xsh->getPlacement(parentId, frame);
}

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

TAffine TTool::getColumnMatrix(int columnIndex) const
{
	if (!m_application)
		return TAffine();

	TFrameHandle *fh = m_application->getCurrentFrame();
	if (fh->isEditingLevel())
		return TAffine();
	int frame = fh->getFrame();
	TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();
	TStageObjectId columnId = TStageObjectId::ColumnId(columnIndex);
	TAffine columnPlacement = xsh->getPlacement(columnId, frame);
	double columnZ = xsh->getZ(columnId, frame);

	TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
	TStageObject *camera = xsh->getStageObject(cameraId);
	TAffine cameraPlacement = camera->getPlacement(frame);
	double cameraZ = camera->getZ(frame);

	TStageObject *object = xsh->getStageObject(columnId);
	TAffine placement;
	TStageObject::perspective(
		placement,
		cameraPlacement, cameraZ,
		columnPlacement, columnZ,
		object->getGlobalNoScaleZ());

	return placement;
}

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

TAffine TTool::getCurrentObjectParentMatrix2() const
{
	TTool::Application *app = m_application;
	TFrameHandle *fh = app->getCurrentFrame();
	if (fh->isEditingLevel())
		return TAffine();
	int frame = fh->getFrame();
	TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
	TStageObjectId id = app->getCurrentObject()->getObjectId();
	double objZ = xsh->getZ(id, frame);
	TStageObjectId parentId = xsh->getStageObjectParent(id);
	if (parentId == TStageObjectId::NoneId)
		return TAffine();
	id = parentId;
	TAffine objPlacement = xsh->getPlacement(id, frame);

	TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
	TStageObject *camera = xsh->getStageObject(cameraId);
	TAffine cameraPlacement = camera->getPlacement(frame);
	double cameraZ = camera->getZ(frame);

	TAffine placement;
	TStageObject::perspective(
		placement,
		cameraPlacement, cameraZ,
		objPlacement, objZ,
		0);
	return placement;
}

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

void TTool::updateMatrix()
{
	assert(m_application);

	if (m_application->getCurrentObject()->isSpline())
		setMatrix(getCurrentObjectParentMatrix2());
	else
		setMatrix(getCurrentColumnMatrix());
}

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

void TTool::resetInputMethod()
{
	if (m_viewer)
		m_viewer->resetInputMethod();
}

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

bool TTool::isColumnLocked(int columnIndex) const
{
	if (columnIndex < 0)
		return false;
	TXsheet *xsh = getXsheet();
	TXshColumn *column = xsh->getColumn(columnIndex);
	return column->isLocked();
}

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

QString TTool::updateEnabled()
{
	// Disable every tool during playback
	if (m_application->getCurrentFrame()->isPlaying())
		return (enable(false), QString());

	// Release Generic tools at once
	int toolType = getToolType();
	int targetType = getTargetType();

	if (toolType == TTool::GenericTool)
		return (enable(true), QString());

	// Retrieve vars and view modes
	TXsheet *xsh = m_application->getCurrentXsheet()->getXsheet();

	int rowIndex = m_application->getCurrentFrame()->getFrame();
	int columnIndex = m_application->getCurrentColumn()->getColumnIndex();
	TXshColumn *column = (columnIndex >= 0) ? xsh->getColumn(columnIndex) : 0;

	TXshLevel *xl = m_application->getCurrentLevel()->getLevel();
	TXshSimpleLevel *sl = xl ? xl->getSimpleLevel() : 0;
	int levelType = sl ? sl->getType() : NO_XSHLEVEL;

	TStageObject *obj = xsh->getStageObject(TStageObjectId::ColumnId(columnIndex));
	bool spline = m_application->getCurrentObject()->isSpline();

	bool filmstrip = m_application->getCurrentFrame()->isEditingLevel();

	/*-- MultiLayerStylePickerONのときは、現状に関わらず使用可能 --*/
	if (m_name == T_StylePicker && Preferences::instance()->isMultiLayerStylePickerEnabled())
		return (enable(true), QString());

	// Check against unplaced columns (not in filmstrip mode)
	if (column && !filmstrip) {
		if (column->isLocked())
			return (enable(false), QObject::tr("The current column is locked."));

		else if (column->getSoundColumn())
			return (enable(false), QObject::tr("It is not possible to edit the audio column."));

		else if (column->getSoundTextColumn())
			return (enable(false), QObject::tr("It is not possible to edit the Magpie column."));

		if (toolType == TTool::ColumnTool) {
			// Check column target
			if (column->getLevelColumn() && !(targetType & LevelColumns))
				return (enable(false), QObject::tr("The current tool cannot be used on a Level column."));

			if (column->getMeshColumn() && !(targetType & MeshColumns))
				return (enable(false), QObject::tr("The current tool cannot be used on a Mesh column."));
		}
	}

	// Check column tools
	if (toolType == TTool::ColumnTool) {
		if (filmstrip)
			return (enable(false), QObject::tr("The current tool cannot be used in Level Strip mode."));

		if ((!column || column->isEmpty()) && !(targetType & TTool::EmptyTarget))
			return (enable(false), QString());
	}

	// Check LevelRead & LevelWrite tools
	if (toolType & TTool::LevelTool) {
		// Check against splines
		if (spline) {
			return (targetType & Splines) ? (enable(true), QString()) : (enable(false), QObject::tr("The current tool cannot be used to edit a motion path."));
		}

		// Check against empty levels
		if (!xl)
			return (targetType & EmptyTarget) ? (enable(true), QString()) : (enable(false), QString());

		// Check against simple-level-edness
		if (!sl)
			return (enable(false), QObject::tr("The current level is not editable.")); // Does it happen at all btw?

		// Check against level types
		{
			if ((levelType == PLI_XSHLEVEL) && !(targetType & VectorImage))
				return (enable(false), QObject::tr("The current tool cannot be used on a Vector Level."));

			if ((levelType == TZP_XSHLEVEL) && !(targetType & ToonzImage))
				return (enable(false), QObject::tr("The current tool cannot be used on a Toonz Level."));

			if ((levelType == OVL_XSHLEVEL) && !(targetType & RasterImage))
				return (enable(false), QObject::tr("The current tool cannot be used on a Raster Level."));

			if ((levelType == MESH_XSHLEVEL) && !(targetType & MeshImage))
				return (enable(false), QObject::tr("The current tool cannot be used on a Mesh Level."));
		}

		// Check against impossibly traceable movements on the column
		if ((levelType & LEVELCOLUMN_XSHLEVEL) && !filmstrip) {
			// Test for Mesh-deformed levels
			const TStageObjectId &parentId = obj->getParent();
			if (parentId.isColumn() && obj->getParentHandle()[0] != 'H') {
				TXshSimpleLevel *parentSl = xsh->getCell(rowIndex, parentId.getIndex()).getSimpleLevel();
				if (parentSl && parentSl->getType() == MESH_XSHLEVEL)
					return (enable(false), QObject::tr("The current tool cannot be used on a mesh-deformed level"));
			}
		}

		// Check TTool::ImageType tools
		if (toolType == TTool::LevelWriteTool) {
			// Check level against read-only status
			if (sl->isReadOnly()) {
				const std::set<TFrameId> &editableFrames = sl->getEditableRange();
				TFrameId currentFid = getCurrentFid();

				if (editableFrames.find(currentFid) == editableFrames.end())
					return (enable(false), QObject::tr("The current frame is locked: any editing is forbidden."));
			}

			// Check level type write support
			if (sl->getPath().getType() == "psd" || // We don't have the API to write psd files
				sl->is16BitChannelLevel() ||		// Inherited by previous implementation. Could be fixed?
				sl->getProperties()->getBpp() == 1) // Black & White images. Again, could be fixed?

				return (enable(false), QObject::tr("The current level is not editable."));
		}
	}

	return (enable(true), QString());
}

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

void TTool::setSelectedFrames(const std::set<TFrameId> &selectedFrames)
{
	m_selectedFrames = selectedFrames;
	onSelectedFramesChanged();
}