#include "xsheetdragtool.h"
// Tnz6 includes
#include "xsheetviewer.h"
#include "cellselection.h"
#include "columncommand.h"
#include "keyframeselection.h"
#include "cellkeyframeselection.h"
#include "tapp.h"
#include "xshcolumnviewer.h"
#include "columnselection.h"
#include "filmstripselection.h"
#include "castselection.h"
#include "iocommand.h"
#include "keyframemover.h"
#include "xshcellmover.h"
// TnzTools includes
#include "tools/cursors.h"
#include "tools/cursormanager.h"
// TnzQt includes
#include "toonzqt/tselectionhandle.h"
#include "historytypes.h"
// TnzLib includes
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tonionskinmaskhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/toonzscene.h"
#include "toonz/txsheet.h"
#include "toonz/txshcolumn.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/txshcell.h"
#include "toonz/fxdag.h"
#include "toonz/sceneproperties.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/tstageobjectkeyframe.h"
#include "toonz/onionskinmask.h"
#include "toonz/txshsoundcolumn.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshnoteset.h"
#include "toutputproperties.h"
#include "toonz/preferences.h"
#include "toonz/columnfan.h"
#include "toonz/navigationtags.h"
// TnzBase includes
#include "tfx.h"
// TnzCore includes
#include "tundo.h"
// Qt includes
#include <QPainter>
#include <QMouseEvent>
#include <QUrl>
#include <QDropEvent>
//=============================================================================
// XsheetGUI DragTool
//-----------------------------------------------------------------------------
XsheetGUI::DragTool::DragTool(XsheetViewer *viewer) : m_viewer(viewer) {}
//-----------------------------------------------------------------------------
XsheetGUI::DragTool::~DragTool() {}
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::refreshCellsArea() { getViewer()->updateCells(); }
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::refreshColumnsArea() { getViewer()->updateColumns(); }
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::refreshRowsArea() { getViewer()->updateRows(); }
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::onClick(const QMouseEvent *event) {
QPoint xy = event->pos();
CellPosition pos = getViewer()->xyToPosition(xy);
onClick(pos);
}
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::onDrag(const QMouseEvent *event) {
QPoint xy = event->pos();
CellPosition pos = getViewer()->xyToPosition(xy);
onDrag(pos);
}
//-----------------------------------------------------------------------------
void XsheetGUI::DragTool::onRelease(const QMouseEvent *event) {
QPoint xy = event->pos();
CellPosition pos = getViewer()->xyToPosition(xy);
onRelease(pos);
}
//=============================================================================
// XsheetSelection DragTool
//-----------------------------------------------------------------------------
class XsheetSelectionDragTool final : public XsheetGUI::DragTool {
int m_firstRow, m_firstCol;
Qt::KeyboardModifiers m_modifier;
public:
XsheetSelectionDragTool(XsheetViewer *viewer)
: DragTool(viewer), m_firstRow(0), m_firstCol(0), m_modifier() {}
// activate when clicked the cell
void onClick(const QMouseEvent *event) override {
m_modifier = event->modifiers();
CellPosition pos = getViewer()->xyToPosition(event->pos());
int row = pos.frame();
int col = pos.layer();
m_firstCol = col;
m_firstRow = row;
// First, check switching of the selection types. This may clear the
// previous selection.
if (m_modifier & Qt::ControlModifier)
getViewer()->getCellKeyframeSelection()->makeCurrent();
else
getViewer()->getCellSelection()->makeCurrent();
if (m_modifier & Qt::ShiftModifier) {
int r0, c0, r1, c1;
getViewer()->getCellSelection()->getSelectedCells(r0, c0, r1, c1);
if (r0 <= r1 && c0 <= c1) {
if (abs(row - r0) < abs(row - r1)) {
m_firstRow = r1;
r0 = row;
} else {
m_firstRow = r0;
r1 = row;
}
if (abs(col - c0) < abs(col - c1)) {
m_firstCol = c1;
c0 = col;
} else {
m_firstCol = c0;
c1 = col;
}
if (m_modifier & Qt::ControlModifier)
getViewer()->getCellKeyframeSelection()->selectCellsKeyframes(r0, c0,
r1, c1);
else
getViewer()->getCellSelection()->selectCells(r0, c0, r1, c1);
} else {
if (m_modifier & Qt::ControlModifier)
getViewer()->getCellKeyframeSelection()->selectCellsKeyframes(
row, col, row, col);
else
getViewer()->getCellSelection()->selectCells(row, col, row, col);
getViewer()->setCurrentColumn(col);
if (Preferences::instance()->isMoveCurrentEnabled())
getViewer()->setCurrentRow(row);
}
} else {
getViewer()->setCurrentColumn(col);
if (Preferences::instance()->isMoveCurrentEnabled())
getViewer()->setCurrentRow(row);
if (m_modifier & Qt::ControlModifier)
getViewer()->getCellKeyframeSelection()->selectCellKeyframe(row, col);
else
getViewer()->getCellSelection()->selectCell(row, col);
}
refreshCellsArea();
refreshRowsArea();
}
void onDrag(const CellPosition &pos) override {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int row = pos.frame(), col = pos.layer();
int firstCol =
Preferences::instance()->isXsheetCameraColumnVisible() ? -1 : 0;
if (col < firstCol || (!getViewer()->orientation()->isVerticalTimeline() &&
col >= xsh->getColumnCount()))
return;
if (row < 0) row = 0;
if (m_modifier & Qt::ControlModifier)
getViewer()->getCellKeyframeSelection()->selectCellsKeyframes(
m_firstRow, m_firstCol, row, col);
else
getViewer()->getCellSelection()->selectCells(m_firstRow, m_firstCol, row,
col);
refreshCellsArea();
refreshRowsArea();
}
void onRelease(const QMouseEvent *event) override {
TApp::instance()->getCurrentSelection()->notifySelectionChanged();
refreshRowsArea();
}
};
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeSelectionTool(
XsheetViewer *viewer) {
return new XsheetSelectionDragTool(viewer);
}
//-----------------------------------------------------------------------------
namespace {
class UndoPlayRangeModifier final : public TUndo {
int m_oldR0, m_oldR1, m_newR0, m_newR1, m_newStep, m_oldStep;
public:
UndoPlayRangeModifier(int oldR0, int newR0, int oldR1, int newR1, int oldStep,
int newStep, bool oldEnabled, bool newEnabled)
: m_oldR0(oldR0)
, m_newR0(newR0)
, m_oldR1(oldR1)
, m_newR1(newR1)
, m_oldStep(oldStep)
, m_newStep(newStep) {}
void undo() const override {
XsheetGUI::setPlayRange(m_oldR0, m_oldR1, m_oldStep, false);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
void redo() const override {
XsheetGUI::setPlayRange(m_newR0, m_newR1, m_newStep, false);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
int getSize() const override { return sizeof(*this); }
QString getHistoryString() override {
if (m_oldR0 < 0 || m_oldR1 < 0)
return QObject::tr("Modify Play Range : %1 - %2")
.arg(QString::number(m_newR0 + 1))
.arg(QString::number(m_newR1 + 1));
return QObject::tr("Modify Play Range : %1 - %2 > %3 - %4")
.arg(QString::number(m_oldR0 + 1))
.arg(QString::number(m_oldR1 + 1))
.arg(QString::number(m_newR0 + 1))
.arg(QString::number(m_newR1 + 1));
}
int getHistoryType() override { return HistoryType::Xsheet; }
};
} // namespace
//-----------------------------------------------------------------------------
void XsheetGUI::setPlayRange(int r0, int r1, int step, bool withUndo) {
if (r0 > r1) {
r0 = 0;
r1 = -1;
}
if (withUndo) {
int oldR0, oldR1, oldStep;
XsheetGUI::getPlayRange(oldR0, oldR1, oldStep);
TUndoManager::manager()->add(new UndoPlayRangeModifier(
oldR0, r0, oldR1, r1, oldStep, step, true, true));
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
}
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
scene->getProperties()->getPreviewProperties()->setRange(r0, r1, step);
TApp::instance()->getCurrentScene()->notifySceneChanged();
}
//-----------------------------------------------------------------------------
bool XsheetGUI::getPlayRange(int &r0, int &r1, int &step) {
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
scene->getProperties()->getPreviewProperties()->getRange(r0, r1, step);
if (r0 > r1) {
r0 = -1;
r1 = -2;
return false;
} else
return true;
}
//-----------------------------------------------------------------------------
bool XsheetGUI::isPlayRangeEnabled() {
int playR0, playR1, step;
XsheetGUI::getPlayRange(playR0, playR1, step);
return playR0 <= playR1;
}
//=============================================================================
// LevelMover tool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// CellMovementUndo
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeLevelMoverTool(
XsheetViewer *viewer) {
return new LevelMoverTool(viewer);
}
//=============================================================================
// LevelExtender DragTool
//-----------------------------------------------------------------------------
namespace {
//-----------------------------------------------------------------------------
//=============================================================================
// LevelExtenderUndo
//-----------------------------------------------------------------------------
class LevelExtenderUndo final : public TUndo {
int m_colCount;
int m_rowCount;
int m_col, m_row, m_deltaRow;
std::vector<TXshCell> m_cells; // righe x colonne
bool m_insert;
bool m_invert; // upper-directional
public:
LevelExtenderUndo(bool insert = true, bool invert = false)
: m_colCount(0)
, m_rowCount(0)
, m_col(0)
, m_row(0)
, m_deltaRow(0)
, m_insert(insert)
, m_invert(invert) {}
void setCells(TXsheet *xsh, int row, int col, int rowCount, int colCount) {
assert(rowCount > 0 && colCount > 0);
m_row = row;
m_col = col;
m_rowCount = rowCount;
m_colCount = colCount;
m_cells.resize(rowCount * colCount, TXshCell());
int k = 0;
for (int r = row; r < row + rowCount; r++)
for (int c = col; c < col + colCount; c++)
m_cells[k++] = xsh->getCell(r, c);
}
void setDeltaRow(int drow) {
assert(drow != 0);
m_deltaRow = drow;
}
void removeCells() const {
assert(m_deltaRow != 0);
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int count = abs(m_deltaRow);
int r = m_row + m_rowCount - count;
for (int c = m_col; c < m_col + m_colCount; c++) {
// Se e' una colonna sound l'extender non deve fare nulla.
TXshColumn *column = xsh->getColumn(c);
if (column && column->getSoundColumn()) continue;
xsh->removeCells(r, c, count);
}
}
void insertCells() const {
assert(m_deltaRow != 0);
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int count = abs(m_deltaRow);
int r0 = m_row + m_rowCount - count;
int r1 = m_row + m_rowCount - 1;
for (int c = 0; c < m_colCount; c++) {
// Se e' una colonna sound l'extender non deve fare nulla.
TXshColumn *column = xsh->getColumn(c);
if (column && column->getSoundColumn()) continue;
int col = m_col + c;
xsh->insertCells(r0, col, count);
int r;
for (r = r0; r <= r1; r++) {
int k = (r - m_row) * m_colCount + c;
xsh->setCell(r, col, m_cells[k]);
}
}
}
// for upper-directional smart tab
// also used for non-insert(overwriting) extension
void clearCells() const {
assert(m_deltaRow != 0);
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int count = abs(m_deltaRow);
for (int c = m_col; c < m_col + m_colCount; c++) {
TXshColumn *column = xsh->getColumn(c);
if (column && column->getSoundColumn()) continue;
if (m_invert)
xsh->clearCells(m_row, c, count);
else
xsh->clearCells(m_row + m_rowCount - count, c, count);
}
}
// for upper-directional smart tab
// also used for non-insert(overwriting) extension
void setCells() const {
assert(m_deltaRow != 0);
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int count = abs(m_deltaRow);
int r0, r1;
if (m_invert) {
r0 = m_row;
r1 = m_row + count - 1;
} else {
r0 = m_row + m_rowCount - count - 1;
r1 = m_row + m_rowCount - 1;
}
for (int c = 0; c < m_colCount; c++) {
TXshColumn *column = xsh->getColumn(c);
if (column && column->getSoundColumn()) continue;
int col = m_col + c;
for (int r = r0; r <= r1; r++) {
int k = (r - m_row) * m_colCount + c;
xsh->setCell(r, col, m_cells[k]);
}
}
}
void undo() const override {
// undo for shrinking operation -> revert cells
if (m_deltaRow < 0) (m_insert) ? insertCells() : setCells();
// undo for stretching operation -> remove cells
else if (m_deltaRow > 0)
(m_insert) ? removeCells() : clearCells();
else {
assert(0);
}
TSelection *selection =
TApp::instance()->getCurrentSelection()->getSelection();
if (selection) selection->selectNone();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
void redo() const override {
// redo for shrinking operation -> remove cells
if (m_deltaRow < 0) (m_insert) ? removeCells() : clearCells();
// redo for stretching operation -> revert cells
else if (m_deltaRow > 0)
(m_insert) ? insertCells() : setCells();
else {
assert(0);
}
TSelection *selection =
TApp::instance()->getCurrentSelection()->getSelection();
if (selection) selection->selectNone();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
int getSize() const override {
return sizeof(*this) + sizeof(TXshCell) * m_cells.size();
}
QString getHistoryString() override {
return QObject::tr("Use Level Extender");
}
int getHistoryType() override { return HistoryType::Xsheet; }
};
//=============================================================================
// CellBuilder
//-----------------------------------------------------------------------------
class CellBuilder {
std::vector<TXshCell> m_sourceCells;
bool m_sequenceFound;
int m_base, m_inc, m_step, m_offset, m_count;
int m_firstRow;
// upper-directional smart tab
bool m_invert;
public:
// CellBuilder is constructed when the smart tab is clicked.
// row : top row in the selection, col : current column, rowCount :
// selected row count
CellBuilder(TXsheet *xsh, int row, int col, int rowCount, int invert = false)
: m_firstRow(row)
, m_sequenceFound(false)
, m_base(0)
, m_inc(0)
, m_step(0)
, m_offset(0)
, m_count(0)
, m_sourceCells(rowCount, TXshCell())
, m_invert(invert) {
if (!m_invert)
xsh->getCells(row, col, rowCount, &m_sourceCells[0]);
else {
std::vector<TXshCell> cells(rowCount, TXshCell());
xsh->getCells(row, col, rowCount, &cells[0]);
for (int r = 0; r < rowCount; r++)
m_sourceCells[r] = cells[rowCount - 1 - r];
// the "first row" becomes the bottom-most row for the upper-directional
// smart tab
m_firstRow = row + rowCount - 1;
}
analyzeCells();
}
CellBuilder()
: m_firstRow(0)
, m_sequenceFound(false)
, m_base(0)
, m_inc(0)
, m_step(0)
, m_offset(0)
, m_count(0)
, m_sourceCells() {}
TFrameId computeFrame(int i) const {
assert(i >= 0);
int k = i + m_offset;
// if there is a large cycle
if (m_count > 0) k = k % m_count;
k = m_base + (k / m_step) * m_inc;
if (m_inc < 0)
while (k < 1) k += m_base;
return TFrameId(k);
}
void analyzeCells() {
// se e' vuoto non c'e' sequenza
if (m_sourceCells.empty()) return;
TXshCell cell = m_sourceCells[0];
if (!cell.m_level) return;
// controllo che tutte le celle appartengano allo stesso livello
// e che i frameId non abbiano suffissi (es. 0012b)
// there is no rule if there are different levels in the selection or
// letters in the frame numbers
int count = m_sourceCells.size();
int i;
for (i = 1; i < count; i++)
if (m_sourceCells[i].m_level != cell.m_level ||
!m_sourceCells[i].m_frameId.getLetter().isEmpty())
return;
// check if all the selected cells have the same frame number
// 'm_base' e' il primo frame number
m_base = cell.m_frameId.getNumber();
// cerco il primo cambiamento di frame
for (i = 1; i < count; i++)
if (m_sourceCells[i].m_frameId != cell.m_frameId) break;
// there is no rule if all the selected cells are the same.
// se sono tutti uguali la sequenza e' banale (e la tratto come
// se non ci fosse)
if (i == count) return;
// check if there is a incremental rule.
// 'm_inc' e' l'incremento di frame number
m_inc = m_sourceCells[i].m_frameId.getNumber() - m_base;
assert(m_inc != 0);
// 'm_step' e' il numero di volte che viene ripetuto un frame
m_step = i;
// se 'm_offset'>0 vuol dire che m_step>1 e la selezione e' partita m_offset
// celle dopo un cambio di frame
m_offset = 0;
if (m_step > 1) {
//先頭と異なるセルを仮にループ終端とする
TFrameId fid(m_sourceCells[m_step].m_frameId);
//次にループ終端と異なるセルがくるまでセルを送る
for (i++; i < count && m_sourceCells[i].m_frameId == fid; i++) {
}
//次のループ終端候補が見つかった
int step = i - m_step;
//ループ距離が縮んでいたら規則なし
if (step < m_step) return;
m_offset = step - m_step;
m_step = step;
}
// la sequenza potrebbe essere ciclica. cerco di trovare
// l'ultimo elemento
for (; i < count; i++)
if (m_sourceCells[i].m_frameId != computeFrame(i)) break;
// check if there are the double loops
if (i < count) {
TFrameId first(m_base);
if (m_sourceCells[i].m_frameId != first) return;
if (m_step > 1 && ((i + m_offset) % m_step) != 0) return;
m_count = i + m_offset;
// controllo che le celle restanti verifichino la sequenza
for (; i < count; i++)
if (m_sourceCells[i].m_frameId != computeFrame(i)) return;
}
// eureka!
m_sequenceFound = true;
}
TXshCell generate(int row) const {
int i = row - m_firstRow;
if (m_invert) i = -i;
if (i < 0 || m_sourceCells.empty())
return TXshCell();
else if (i < (int)m_sourceCells.size())
return m_sourceCells[i];
else if (m_sequenceFound) {
TXshCell cell;
cell.m_level = m_sourceCells[0].m_level;
cell.m_frameId = computeFrame(i);
return cell;
} else {
i = i % m_sourceCells.size();
return m_sourceCells[i];
}
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
//=============================================================================
// LevelExtenderTool
//-----------------------------------------------------------------------------
class LevelExtenderTool final : public XsheetGUI::DragTool {
int m_colCount;
int m_rowCount;
int m_c0, m_r0, m_r1;
std::vector<CellBuilder> m_columns;
LevelExtenderUndo *m_undo;
bool m_invert; // upper directional smart tab
bool m_insert;
public:
LevelExtenderTool(XsheetViewer *viewer, bool insert = true,
bool invert = false)
: XsheetGUI::DragTool(viewer)
, m_colCount(0)
, m_undo(0)
, m_insert(insert)
, m_invert(invert) {}
// called when the smart tab is clicked
void onClick(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
int r0, c0, r1, c1;
getViewer()->getCellSelection()->getSelectedCells(r0, c0, r1, c1);
if (m_invert)
assert(r0 == row + 1);
else
assert(r1 == row - 1);
m_c0 = c0;
m_r0 = r0;
m_r1 = r1;
m_colCount = c1 - c0 + 1;
m_rowCount = r1 - r0 + 1;
if (m_colCount <= 0 || m_rowCount <= 0) return;
// if m_insert is false but there are no empty rows under the tab,
// then switch m_insert to true so that the operation works anyway
if (!m_insert && !m_invert) {
TXsheet *xsh = getViewer()->getXsheet();
for (int c = c0; c <= c1; c++) {
TXshColumn *column = xsh->getColumn(c);
if (!column || column->getSoundColumn()) continue;
if (!column->isCellEmpty(r1 + 1)) {
m_insert = true; // switch the behavior
break;
}
}
}
m_columns.reserve(m_colCount);
TXsheet *xsh = getViewer()->getXsheet();
for (int c = c0; c <= c1; c++)
m_columns.push_back(CellBuilder(xsh, r0, c, m_rowCount, m_invert));
m_undo = new LevelExtenderUndo(m_insert, m_invert);
m_undo->setCells(xsh, r0, c0, m_rowCount, m_colCount);
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
if (!m_invert)
onCellChange(row, col);
else
onCellChangeInvert(row, col);
refreshCellsArea();
}
void onCellChange(int row, int col) {
if (m_colCount <= 0 || m_rowCount <= 0) return;
if (row <= m_r0) row = m_r0 + 1;
int r1 = row - 1; // r1 e' la riga inferiore della nuova selezione
if (r1 < m_r0) r1 = m_r0;
int dr = r1 - m_r1;
if (dr == 0) return;
TXsheet *xsh = getViewer()->getXsheet();
// shrink
if (dr < 0) {
for (int c = 0; c < m_colCount; c++) {
// Se e' una colonna sound l'extender non deve fare nulla.
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (column && column->getSoundColumn()) continue;
if (m_insert)
xsh->removeCells(row, m_c0 + c, -dr);
else {
for (int r = row; r <= m_r1; r++)
xsh->setCell(r, m_c0 + c, TXshCell());
}
}
}
// extend
else {
// check how many vacant rows
if (!m_insert) {
int tmp_dr;
bool found = false;
for (tmp_dr = 1; tmp_dr <= dr; tmp_dr++) {
for (int c = 0; c < m_colCount; c++) {
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (!column || column->getSoundColumn()) continue;
if (!column->isCellEmpty(m_r1 + tmp_dr)) {
found = true;
break;
}
}
if (found) break;
}
if (tmp_dr == 1) return;
dr = tmp_dr - 1;
r1 = m_r1 + dr;
}
for (int c = 0; c < m_colCount; c++) {
// Se e' una colonna sound l'extender non deve fare nulla.
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (column && column->getSoundColumn()) continue;
if (m_insert) xsh->insertCells(m_r1 + 1, m_c0 + c, dr);
for (int r = m_r1 + 1; r <= r1; r++)
xsh->setCell(r, m_c0 + c, m_columns[c].generate(r));
}
}
m_r1 = r1;
getViewer()->getCellSelection()->selectCells(m_r0, m_c0, m_r1,
m_c0 + m_colCount - 1);
}
// for upper-directinoal smart tab
void onCellChangeInvert(int row, int col) {
if (m_colCount <= 0 || m_rowCount <= 0) return;
if (row >= m_r1) row = m_r1 - 1;
int r0 = row + 1;
if (r0 > m_r1) r0 = m_r1;
if (r0 < 0) r0 = 0;
TXsheet *xsh = getViewer()->getXsheet();
// check how many vacant rows
int emptyRow;
for (emptyRow = m_r0 - 1; emptyRow >= 0; emptyRow--) {
bool found = false;
for (int c = 0; c < m_colCount; c++) {
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (!column || column->getSoundColumn()) continue;
if (!column->isCellEmpty(emptyRow)) {
emptyRow += 1;
found = true;
break;
}
}
if (found) break;
}
// upper-directional tab can extend cells only in the mpty area
if (r0 < emptyRow) r0 = emptyRow;
int dr = r0 - m_r0;
if (dr == 0) return;
// shrink
if (dr > 0) {
// clear cells
for (int c = 0; c < m_colCount; c++) {
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (!column || column->getSoundColumn()) continue;
xsh->clearCells(m_r0, m_c0 + c, dr);
}
}
// extend
else {
for (int c = 0; c < m_colCount; c++) {
TXshColumn *column = xsh->getColumn(m_c0 + c);
if (!column || column->getSoundColumn()) continue;
for (int r = r0; r <= m_r0 - 1; r++) {
xsh->setCell(r, m_c0 + c, m_columns[c].generate(r));
}
}
}
m_r0 = r0;
getViewer()->getCellSelection()->selectCells(m_r0, m_c0, m_r1,
m_c0 + m_colCount - 1);
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
int delta = m_r1 - (m_r0 + m_rowCount - 1);
if (delta == 0)
delete m_undo;
else {
if (delta > 0)
m_undo->setCells(getViewer()->getXsheet(), m_r0, m_c0, m_r1 - m_r0 + 1,
m_colCount);
m_undo->setDeltaRow(delta);
TUndoManager::manager()->add(m_undo);
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
m_undo = 0;
}
};
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeLevelExtenderTool(
XsheetViewer *viewer, bool insert, bool invert) {
return new LevelExtenderTool(viewer, insert, invert);
}
//=============================================================================
// LevelExtender DragTool
//-----------------------------------------------------------------------------
namespace {
//-----------------------------------------------------------------------------
//=============================================================================
// SoundLevelModifierUndo
//-----------------------------------------------------------------------------
class SoundLevelModifierUndo final : public TUndo {
int m_col;
TXshSoundColumnP m_newSoundColumn;
TXshSoundColumnP m_oldSoundColumn;
TXsheetHandle *m_xshHandle;
public:
SoundLevelModifierUndo(int col, TXshSoundColumn *oldSoundColumn)
: m_col(col), m_newSoundColumn(), m_oldSoundColumn(oldSoundColumn) {
m_xshHandle = TApp::instance()->getCurrentXsheet();
}
void setNewColumn(TXshSoundColumn *newSoundcolumn) {
m_newSoundColumn = dynamic_cast<TXshSoundColumn *>(newSoundcolumn->clone());
}
TXshSoundColumn *getColumn() const {
TXsheet *xsh = m_xshHandle->getXsheet();
TXshColumn *column = xsh->getColumn(m_col);
assert(column);
// La colonna sound deve esserci, metto un controllo di sicurezza.
TXshSoundColumn *soundColumn = column->getSoundColumn();
assert(soundColumn);
return soundColumn;
}
void undo() const override {
TXshSoundColumn *soundColumn = getColumn();
if (!soundColumn) return;
soundColumn->assignLevels(m_oldSoundColumn.getPointer());
m_xshHandle->notifyXsheetChanged();
}
void redo() const override {
TXshSoundColumn *soundColumn = getColumn();
if (!soundColumn) return;
soundColumn->assignLevels(m_newSoundColumn.getPointer());
m_xshHandle->notifyXsheetChanged();
}
int getSize() const override { return sizeof(*this); }
QString getHistoryString() override {
return QObject::tr("Modify Sound Level");
}
int getHistoryType() override { return HistoryType::Xsheet; }
};
} // namespace
//=============================================================================
// SoundLevelModifierTool
//-----------------------------------------------------------------------------
class SoundLevelModifierTool final : public XsheetGUI::DragTool {
int m_col;
int m_firstRow;
TXshSoundColumnP m_oldColumn;
SoundLevelModifierUndo *m_undo;
enum ModifierType {
UNKNOWN_TYPE = 0,
START_TYPE = 1,
END_TYPE = 2,
} m_modifierType;
public:
SoundLevelModifierTool(XsheetViewer *viewer)
: XsheetGUI::DragTool(viewer)
, m_col(-1)
, m_firstRow(-1)
, m_oldColumn()
, m_modifierType(UNKNOWN_TYPE)
, m_undo(0) {}
ModifierType getType(int r0, int r1, int delta) {
if (m_firstRow == r0 && m_firstRow == r1) {
if (delta == 0)
return UNKNOWN_TYPE;
else if (delta < 0)
return START_TYPE;
else
return END_TYPE;
}
if (m_firstRow == r0)
return START_TYPE;
else if (m_firstRow == r1)
return END_TYPE;
return UNKNOWN_TYPE;
}
~SoundLevelModifierTool() { delete m_undo; }
TXshSoundColumn *getColumn() const {
TXshColumn *column = getViewer()->getXsheet()->getColumn(m_col);
assert(column);
// La colonna sound deve esserci, metto un controllo di sicurezza.
TXshSoundColumn *soundColumn = column->getSoundColumn();
assert(soundColumn);
return soundColumn;
}
void onClick(const CellPosition &pos) override {
m_firstRow = pos.frame();
m_col = pos.layer();
TXshSoundColumn *soundColumn = getColumn();
if (!soundColumn) return;
m_oldColumn = dynamic_cast<TXshSoundColumn *>(soundColumn->clone());
m_undo = new SoundLevelModifierUndo(m_col, m_oldColumn.getPointer());
getViewer()->update();
}
void onDrag(const CellPosition &pos) override {
onChange(pos.frame());
refreshCellsArea();
}
void onChange(int row) {
TXshSoundColumn *soundColumn = getColumn();
if (!soundColumn) return;
int delta = row - m_firstRow;
if (delta == 0) {
soundColumn->assignLevels(m_oldColumn.getPointer());
return;
}
int r0, r1;
m_oldColumn->getLevelRange(m_firstRow, r0, r1);
ModifierType type = getType(r0, r1, delta);
if (m_modifierType == UNKNOWN_TYPE && type != UNKNOWN_TYPE)
m_modifierType = type;
else if (m_modifierType != type || type == UNKNOWN_TYPE)
return;
soundColumn->assignLevels(m_oldColumn.getPointer());
soundColumn->modifyCellRange(m_firstRow, delta,
m_modifierType == START_TYPE);
TApp::instance()->getCurrentXsheet()->notifyXsheetSoundChanged();
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame();
if (row - m_firstRow == 0) {
m_undo = 0;
return;
}
TXshColumn *column = getViewer()->getXsheet()->getColumn(m_col);
TXshSoundColumn *soundColumn = getColumn();
if (!soundColumn) return;
soundColumn->updateCells(row, 1);
m_undo->setNewColumn(soundColumn);
TUndoManager::manager()->add(m_undo);
m_undo = 0;
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
};
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeSoundLevelModifierTool(
XsheetViewer *viewer) {
return new SoundLevelModifierTool(viewer);
}
//=============================================================================
// KeyFrame Mover DragTool
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeKeyframeMoverTool(
XsheetViewer *viewer) {
return new KeyframeMoverTool(viewer);
}
//=============================================================================
// Cell KeyFrame Mover DragTool
//-----------------------------------------------------------------------------
class CellKeyframeMoverTool final : public LevelMoverTool {
KeyframeMoverTool *m_keyframeMoverTool;
protected:
bool canMove(const TPoint &pos) override {
if (!m_keyframeMoverTool->canMove(pos)) return false;
return LevelMoverTool::canMove(pos);
}
public:
CellKeyframeMoverTool(XsheetViewer *viewer) : LevelMoverTool(viewer) {
m_keyframeMoverTool = new KeyframeMoverTool(viewer, true);
}
void onClick(const QMouseEvent *e) override {
LevelMoverTool::onClick(e);
m_keyframeMoverTool->onClick(e);
}
void onDrag(const QMouseEvent *e) override {
LevelMoverTool::onDrag(e);
if (m_validPos) m_keyframeMoverTool->onDrag(e);
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
TUndoManager::manager()->beginBlock();
LevelMoverTool::onRelease(pos);
m_keyframeMoverTool->onRelease(pos);
TUndoManager::manager()->endBlock();
}
void drawCellsArea(QPainter &p) override {
LevelMoverTool::drawCellsArea(p);
m_keyframeMoverTool->drawCellsArea(p);
}
};
//=============================================================================
XsheetGUI::DragTool *XsheetGUI::DragTool::makeCellKeyframeMoverTool(
XsheetViewer *viewer) {
return new CellKeyframeMoverTool(viewer);
}
//=============================================================================
// KeyFrame Handle Mover DragTool
//-----------------------------------------------------------------------------
namespace {
//-----------------------------------------------------------------------------
//=============================================================================
// KeyFrameHandleUndo
//-----------------------------------------------------------------------------
class KeyFrameHandleUndo final : public TUndo {
TStageObjectId m_objId;
int m_row;
TStageObject::Keyframe m_oldKeyframe, m_newKeyframe;
public:
KeyFrameHandleUndo(TStageObjectId id, int row) : m_objId(id), m_row(row) {
TStageObject *pegbar =
TApp::instance()->getCurrentXsheet()->getXsheet()->getStageObject(
m_objId);
assert(pegbar);
m_oldKeyframe = pegbar->getKeyframe(m_row);
}
void onAdd() override {
TStageObject *pegbar =
TApp::instance()->getCurrentXsheet()->getXsheet()->getStageObject(
m_objId);
assert(pegbar);
m_newKeyframe = pegbar->getKeyframe(m_row);
}
void setKeyframe(const TStageObject::Keyframe &k) const {
TStageObject *pegbar =
TApp::instance()->getCurrentXsheet()->getXsheet()->getStageObject(
m_objId);
assert(pegbar);
pegbar->setKeyframeWithoutUndo(m_row, k);
TApp::instance()->getCurrentObject()->notifyObjectIdChanged(false);
}
void undo() const override { setKeyframe(m_oldKeyframe); }
void redo() const override { setKeyframe(m_newKeyframe); }
int getSize() const override { return sizeof *this; }
QString getHistoryString() override {
return QObject::tr("Move keyframe handle : %1 Handle of the keyframe %2")
.arg(QString::fromStdString(m_objId.toString()))
.arg(QString::number(m_row + 1));
}
int getHistoryType() override { return HistoryType::Xsheet; }
};
//=============================================================================
// KeyFrameHandleMoverTool
//-----------------------------------------------------------------------------
class KeyFrameHandleMoverTool final : public XsheetGUI::DragTool {
public:
enum HandleType { EaseOut = 0, EaseIn = 1 };
private:
TStageObject *m_stageObject;
TStageObject::Keyframe m_k;
int m_startRow; // E' la riga corrispondente alla key della handle corrente!
HandleType m_handleType;
KeyFrameHandleUndo *m_undo;
int m_r0, m_r1; // m_r0 e' la riga in cui si clicca, m_r1 e' la riga che
// varia nel drag
bool m_enable;
public:
KeyFrameHandleMoverTool(XsheetViewer *viewer, HandleType handleType,
int keyRow)
: XsheetGUI::DragTool(viewer)
, m_stageObject(0)
, m_handleType(handleType)
, m_startRow(keyRow)
, m_r0(0)
, m_r1(0)
, m_enable(true) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
m_r0 = m_r1 = row;
TXsheet *xsh = getViewer()->getXsheet();
TStageObjectId cameraId =
TStageObjectId::CameraId(xsh->getCameraColumnIndex());
TStageObjectId objId = col >= 0 ? TStageObjectId::ColumnId(col) : cameraId;
if (xsh->getColumn(col) && xsh->getColumn(col)->isLocked()) {
m_enable = false;
return;
}
m_stageObject = xsh->getStageObject(objId);
assert(m_stageObject);
m_k = m_stageObject->getKeyframe(m_startRow);
m_undo = new KeyFrameHandleUndo(objId, m_startRow);
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
if (!m_enable) return;
m_r1 = row;
onCellChange(row, col);
TApp::instance()->getCurrentObject()->notifyObjectIdChanged(true);
refreshCellsArea();
}
void onCellChange(int row, int col) {
int dr = row - m_startRow;
if (m_handleType == EaseOut) {
if (dr <= 0) dr = 0;
if (m_k.m_easeOut == dr) return;
m_k.m_easeOut = dr;
} else {
if (dr >= 0) dr = 0;
if (m_k.m_easeIn == dr) return;
m_k.m_easeIn = -dr;
}
m_stageObject->setKeyframeWithoutUndo(m_startRow, m_k);
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
if (!m_enable) return;
if (m_r0 == m_r1)
delete m_undo;
else {
TUndoManager::manager()->add(m_undo);
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
}
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeKeyFrameHandleMoverTool(
XsheetViewer *viewer, bool isEaseOut, int keyRow) {
return new KeyFrameHandleMoverTool(viewer,
isEaseOut
? KeyFrameHandleMoverTool::EaseOut
: KeyFrameHandleMoverTool::EaseIn,
keyRow);
}
//=============================================================================
// OnionSkinMaskModifier tool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// OnionSkinMaskModifierTool
//-----------------------------------------------------------------------------
class NoteMoveTool final : public XsheetGUI::DragTool {
TPointD m_startPos, m_delta;
int m_startRow, m_startCol;
public:
NoteMoveTool(XsheetViewer *viewer) : DragTool(viewer) {}
void onClick(const QMouseEvent *e) override {
TXshNoteSet *notes = getViewer()->getXsheet()->getNotes();
int currentIndex = getViewer()->getCurrentNoteIndex();
m_startPos = notes->getNotePos(currentIndex);
m_startRow = notes->getNoteRow(currentIndex);
m_startCol = notes->getNoteCol(currentIndex);
QPoint p = e->pos();
TPointD mousePos(p.x(), p.y());
QPoint xy = getViewer()->positionToXY(CellPosition(m_startRow, m_startCol));
TPointD cellTopLeft(xy.x(), xy.y());
m_delta = mousePos - (cellTopLeft + m_startPos);
}
void onChange(TPointD pos) {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
pos = pos - m_delta;
CellPosition cellPosition = getViewer()->xyToPosition(pos);
int row = cellPosition.frame();
int col = cellPosition.layer();
if (row < 0) row = 0;
if (col < 0 || (!getViewer()->orientation()->isVerticalTimeline() &&
(col == 0 || col >= xsh->getColumnCount())))
return;
QPoint xy = getViewer()->positionToXY(CellPosition(row, col));
TPointD newPos = pos - TPointD(xy.x(), xy.y());
if (newPos.x < 0) newPos.x = 0;
if (newPos.y < 0) newPos.y = 0;
TXshNoteSet *notes = getViewer()->getXsheet()->getNotes();
int currentIndex = getViewer()->getCurrentNoteIndex();
notes->setNoteRow(currentIndex, row);
notes->setNoteCol(currentIndex, col);
notes->setNotePos(currentIndex, newPos);
}
void onDrag(const QMouseEvent *event) override {
QPoint p = event->pos();
onChange(TPointD(p.x(), p.y()));
refreshCellsArea();
}
void onRelease(const QMouseEvent *event) override {
QPoint p = event->pos();
onChange(TPointD(p.x(), p.y()));
TXshNoteSet *notes = getViewer()->getXsheet()->getNotes();
int currentIndex = getViewer()->getCurrentNoteIndex();
TPointD pos = notes->getNotePos(currentIndex);
int row = notes->getNoteRow(currentIndex);
int col = notes->getNoteCol(currentIndex);
if (m_startPos == pos && m_startRow == row && m_startCol == col) return;
refreshCellsArea();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeNoteMoveTool(
XsheetViewer *viewer) {
return new NoteMoveTool(viewer);
}
//=============================================================================
// OnionSkinMaskModifier tool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// OnionSkinMaskModifierTool
//-----------------------------------------------------------------------------
class OnionSkinMaskModifierTool final : public XsheetGUI::DragTool {
OnionSkinMaskModifier m_modifier;
bool m_isFos;
public:
OnionSkinMaskModifierTool(XsheetViewer *viewer, bool isFos)
: DragTool(viewer)
, m_modifier(TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(),
TApp::instance()->getCurrentFrame()->getFrame())
, m_isFos(isFos) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame();
OnionSkinMask mask = m_modifier.getMask();
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(mask);
m_modifier.click(row, m_isFos);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame();
if (row < 0) row = 0;
onRowChange(row);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
void onRowChange(int row) {
m_modifier.drag(row);
OnionSkinMask mask = m_modifier.getMask();
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(mask);
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame();
m_modifier.release(row);
OnionSkinMask mask = m_modifier.getMask();
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(mask);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeKeyOnionSkinMaskModifierTool(
XsheetViewer *viewer, bool isFos) {
return new OnionSkinMaskModifierTool(viewer, isFos);
}
//=============================================================================
// CurrentFrameModifier tool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// CurrentFrameModifier
//-----------------------------------------------------------------------------
class CurrentFrameModifier final : public XsheetGUI::DragTool {
public:
CurrentFrameModifier(XsheetViewer *viewer) : DragTool(viewer) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame();
TApp::instance()->getCurrentFrame()->setFrame(row);
refreshRowsArea();
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame();
if (row < 0) row = 0;
int lastRow = TApp::instance()->getCurrentFrame()->getFrameIndex();
if (lastRow == row) return;
onRowChange(row);
refreshRowsArea();
}
void onRowChange(int row) {
TApp *app = TApp::instance();
app->getCurrentFrame()->setFrame(row);
int columnIndex = app->getCurrentColumn()->getColumnIndex();
app->getCurrentFrame()->scrubXsheet(row, row, getViewer()->getXsheet());
}
void onRelease(const CellPosition &pos) override {
getViewer()->getXsheet()->stopScrub();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeCurrentFrameModifierTool(
XsheetViewer *viewer) {
return new CurrentFrameModifier(viewer);
}
//=============================================================================
// PlayRangeModifier tool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// PlayRangeModifier
//-----------------------------------------------------------------------------
class PlayRangeModifier final : public XsheetGUI::DragTool {
bool m_movingFirst;
int m_oldR0, m_oldR1, m_oldStep;
public:
PlayRangeModifier(XsheetViewer *viewer)
: DragTool(viewer), m_movingFirst(false) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame();
XsheetGUI::getPlayRange(m_oldR0, m_oldR1, m_oldStep);
assert(m_oldR0 == row || m_oldR1 == row);
m_movingFirst = m_oldR0 == row;
refreshRowsArea();
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame();
if (row < 0) row = 0;
onRowChange(row);
refreshRowsArea();
}
void onRowChange(int row) {
if (row < 0) return;
int r0, r1, step;
XsheetGUI::getPlayRange(r0, r1, step);
if (m_movingFirst) {
if (row <= r1)
r0 = row;
else if (row > r1) {
r0 = (r1 > 0) ? r1 : 0;
r1 = row;
m_movingFirst = false;
}
} else {
if (row >= r0)
r1 = row;
else if (row < r0) {
r1 = r0;
r0 = row;
m_movingFirst = true;
}
}
XsheetGUI::setPlayRange(r0, r1, step, false);
}
void onRelease(const CellPosition &pos) override {
int row = pos.frame();
int newR0, newR1, newStep;
XsheetGUI::getPlayRange(newR0, newR1, newStep);
if (m_oldR0 != newR0 || m_oldR1 != newR1) {
TUndoManager::manager()->add(new UndoPlayRangeModifier(
m_oldR0, newR0, m_oldR1, newR1, m_oldStep, newStep, true, true));
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
}
refreshRowsArea();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makePlayRangeModifierTool(
XsheetViewer *viewer) {
return new PlayRangeModifier(viewer);
}
//=============================================================================
// ColumnSelectionTool
//-----------------------------------------------------------------------------
namespace {
class ColumnSelectionTool final : public XsheetGUI::DragTool {
int m_firstColumn;
bool m_enabled;
public:
ColumnSelectionTool(XsheetViewer *viewer)
: DragTool(viewer), m_firstColumn(-1), m_enabled(false) {}
void onClick(const QMouseEvent *event) override {
TColumnSelection *selection = getViewer()->getColumnSelection();
CellPosition cellPosition = getViewer()->xyToPosition(event->pos());
int col = cellPosition.layer();
m_firstColumn = col;
bool isSelected = selection->isColumnSelected(col);
if (event->modifiers() & Qt::ControlModifier) {
selection->selectColumn(col, !isSelected);
} else if (event->modifiers() & Qt::ShiftModifier) {
// m_enabled = true;
if (isSelected) return;
int ia = col, ib = col;
int columnCount = getViewer()->getXsheet()->getColumnCount();
while (ia > 0 && !selection->isColumnSelected(ia - 1)) --ia;
if (ia == 0) ia = col;
while (ib < columnCount - 1 && !selection->isColumnSelected(ib + 1)) ++ib;
if (ib == columnCount - 1) ib = col;
int i;
for (i = ia; i <= ib; i++) selection->selectColumn(i, true);
} else {
m_enabled = true;
selection->selectNone();
selection->selectColumn(col, true);
}
selection->makeCurrent();
getViewer()->update();
}
void onDrag(const CellPosition &pos) override {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int col = pos.layer();
if (!m_enabled) return;
int firstCol =
Preferences::instance()->isXsheetCameraColumnVisible() ? -1 : 0;
if (col < firstCol || (!getViewer()->orientation()->isVerticalTimeline() &&
col >= xsh->getColumnCount()))
return;
TColumnSelection *selection = getViewer()->getColumnSelection();
selection->selectNone();
int i, ia = m_firstColumn, ib = col;
if (ia > ib) std::swap(ia, ib);
for (i = ia; i <= ib; i++) selection->selectColumn(i, true);
getViewer()->update();
refreshCellsArea();
return;
}
void onRelease(const CellPosition &pos) override {
TSelectionHandle::getCurrent()->notifySelectionChanged();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeColumnSelectionTool(
XsheetViewer *viewer) {
return new ColumnSelectionTool(viewer);
}
//=============================================================================
// Column Movement
//-----------------------------------------------------------------------------
static void moveColumns(const std::set<int> &indices, int delta) {
if (indices.empty()) return;
if (delta < 0 && *indices.begin() + delta < 0) delta = -*indices.begin();
if (delta == 0) return;
TApp *app = TApp::instance();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
std::vector<int> ii;
if (delta > 0)
ii.assign(indices.rbegin(), indices.rend());
else
ii.assign(indices.begin(), indices.end());
int i, m = ii.size();
for (i = 0; i < m; i++) {
int a = ii[i];
int b = a + delta;
xsh->moveColumn(a, b);
}
int col = app->getCurrentColumn()->getColumnIndex();
if (indices.count(col) > 0)
app->getCurrentColumn()->setColumnIndex(col + delta);
}
//-----------------------------------------------------------------------------
class ColumnMoveUndo final : public TUndo {
std::set<int> m_indices;
int m_delta;
public:
// nota: indices sono gli indici DOPO aver fatto il movimento
ColumnMoveUndo(const std::set<int> &indices, int delta)
: m_indices(indices), m_delta(delta) {
assert(delta != 0);
assert(!indices.empty());
assert(*indices.begin() >= 0);
assert(delta < 0 || *indices.begin() - delta >= 0);
}
void undo() const override {
moveColumns(m_indices, -m_delta);
TSelection *selection =
TApp::instance()->getCurrentSelection()->getSelection();
if (selection) selection->selectNone();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
void redo() const override {
std::set<int> ii;
for (std::set<int>::const_iterator it = m_indices.begin();
it != m_indices.end(); ++it)
ii.insert(*it - m_delta);
moveColumns(ii, m_delta);
TSelection *selection =
TApp::instance()->getCurrentSelection()->getSelection();
if (selection) selection->selectNone();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
int getSize() const override {
return sizeof(*this) + m_indices.size() * sizeof(int);
}
QString getHistoryString() override { return QObject::tr("Move Columns"); }
int getHistoryType() override { return HistoryType::Xsheet; }
};
//-----------------------------------------------------------------------------
class ColumnMoveDragTool final : public XsheetGUI::DragTool {
int m_offset, m_firstCol, m_lastCol, m_origOffset;
public:
ColumnMoveDragTool(XsheetViewer *viewer)
: XsheetGUI::DragTool(viewer)
, m_firstCol(-1)
, m_lastCol(-1)
, m_offset(0)
, m_origOffset(0) {}
void onClick(const QMouseEvent *event) override {
QPoint xy = event->pos();
CellPosition pos = getViewer()->xyToPosition(xy);
int col = pos.layer();
TColumnSelection *selection = getViewer()->getColumnSelection();
if (!selection->isColumnSelected(col)) {
if (event->modifiers() & Qt::ControlModifier) {
selection->selectColumn(col, true);
} else if (event->modifiers() & Qt::ShiftModifier) {
int ia = col, ib = col;
int columnCount = getViewer()->getXsheet()->getColumnCount();
while (ia > 0 && !selection->isColumnSelected(ia - 1)) --ia;
if (ia == 0) ia = col;
while (ib < columnCount - 1 && !selection->isColumnSelected(ib + 1))
++ib;
if (ib == columnCount - 1) ib = col;
int i;
for (i = ia; i <= ib; i++) selection->selectColumn(i, true);
} else {
selection->selectNone();
selection->selectColumn(col);
}
selection->makeCurrent();
}
std::set<int> indices = selection->getIndices();
if (indices.empty()) return;
m_firstCol = m_lastCol = *indices.begin();
assert(m_firstCol >= 0);
m_origOffset = m_offset = m_firstCol - col;
assert(m_lastCol == *indices.begin());
getViewer()->update();
if (!getViewer()->orientation()->isVerticalTimeline())
TUndoManager::manager()->beginBlock();
}
void onDrag(const CellPosition &pos) override {
int col = pos.layer();
TColumnSelection *selection = getViewer()->getColumnSelection();
TApp *app = TApp::instance();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
std::set<int> indices = selection->getIndices();
indices.erase(-1); // Ignore camera column
if (indices.empty()) return;
assert(m_lastCol == *indices.begin());
int currEnd = xsh->getColumnCount() - 1;
int origCol = col;
if (col < 0)
col = 0;
else if (!getViewer()->orientation()->isVerticalTimeline() && col > currEnd)
col = currEnd;
int dCol = col - (m_lastCol - m_offset);
// ignore if the cursor moves in the drag-starting column
if (dCol == 0) return;
if (dCol < 0 &&
!xsh->getColumnFan(getViewer()->orientation())->isActive(col)) {
while (
col != 0 &&
!xsh->getColumnFan(getViewer()->orientation())->isActive(col - 1)) {
col--;
dCol--;
}
}
int newBegin = *indices.begin() + dCol;
int newEnd = *indices.rbegin() + dCol;
if (newBegin < 0)
dCol -= newBegin;
else if (!getViewer()->orientation()->isVerticalTimeline() &&
newEnd > currEnd)
dCol -= (newEnd - currEnd);
// ignore if the dragged columns comes up against the end of column stack
if (dCol == 0) return;
m_lastCol += dCol;
assert(*indices.begin() + dCol >= 0);
moveColumns(indices, dCol);
selection->selectNone();
for (std::set<int>::iterator it = indices.begin(); it != indices.end();
++it)
selection->selectColumn(*it + dCol, true);
}
void onRelease(const CellPosition &pos) override {
int delta = m_lastCol - m_firstCol;
if (delta != 0) {
TColumnSelection *selection = getViewer()->getColumnSelection();
std::set<int> indices = selection->getIndices();
if (!indices.empty()) {
TUndoManager::manager()->add(new ColumnMoveUndo(indices, delta));
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
}
if (!getViewer()->orientation()->isVerticalTimeline())
TUndoManager::manager()->endBlock();
}
};
XsheetGUI::DragTool *XsheetGUI::DragTool::makeColumnMoveTool(
XsheetViewer *viewer) {
return new ColumnMoveDragTool(viewer);
}
//=============================================================================
// Parent Change
//-----------------------------------------------------------------------------
class ChangePegbarParentUndo final : public TUndo {
TStageObjectId m_oldParentId;
TStageObjectId m_newParentId;
TStageObjectId m_child;
public:
ChangePegbarParentUndo(const TStageObjectId &child,
const TStageObjectId &oldParentId,
const TStageObjectId &newParentId)
: m_oldParentId(oldParentId)
, m_newParentId(newParentId)
, m_child(child) {}
void undo() const override {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
xsh->setStageObjectParent(m_child, m_oldParentId);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
void redo() const override {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
xsh->setStageObjectParent(m_child, m_newParentId);
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
int getSize() const override { return sizeof(*this); }
QString getHistoryString() override { return QObject::tr("Change Pegbar"); }
int getHistoryType() override { return HistoryType::Xsheet; }
};
//-----------------------------------------------------------------------------
class ChangePegbarParentDragTool final : public XsheetGUI::DragTool {
int m_firstCol, m_lastCol;
public:
ChangePegbarParentDragTool(XsheetViewer *viewer)
: XsheetGUI::DragTool(viewer), m_firstCol(-1), m_lastCol(-1) {}
void onClick(const CellPosition &pos) override {
m_firstCol = m_lastCol = pos.layer();
}
void onDrag(const CellPosition &pos) override { m_lastCol = pos.layer(); }
void onRelease(const CellPosition &pos) override {
// TUndoManager::manager()->add(new ColumnMoveUndo(indices, delta));
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
TStageObjectId columnId(getViewer()->getObjectId(m_firstCol));
if (m_firstCol == -1)
columnId = TStageObjectId::CameraId(
getViewer()->getXsheet()->getCameraColumnIndex());
TStageObjectId parentId(getViewer()->getObjectId(m_lastCol));
if (m_lastCol == -1)
parentId = TStageObjectId::CameraId(
getViewer()->getXsheet()->getCameraColumnIndex());
if (getViewer()->getXsheet()->getColumn(m_lastCol) &&
getViewer()->getXsheet()->getColumn(m_lastCol)->getSoundColumn())
return;
if (m_firstCol == m_lastCol && m_firstCol != -1) {
// vuol dire che la colonna torna al suo padre di default
// la prima colonna e' attaccata alla pegbar 0 le altre alla pegbar 1.
// brutto che questa cosa sia qui. Bisognerebbe spostarlo altrove (in
// tnzlib)
parentId = TStageObjectId::TableId;
}
if (m_firstCol == -1 && m_lastCol <= -1) {
parentId = TStageObjectId::NoneId;
}
if (parentId == xsh->getStageObjectParent(columnId)) return;
TUndoManager::manager()->add(new ChangePegbarParentUndo(
columnId, xsh->getStageObjectParent(columnId), parentId));
TApp::instance()->getCurrentScene()->setDirtyFlag(true);
xsh->setStageObjectParent(columnId, parentId);
getViewer()->update();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
};
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeColumnLinkTool(
XsheetViewer *viewer) {
return new ChangePegbarParentDragTool(viewer);
}
//=============================================================================
// Volume adjust
//-----------------------------------------------------------------------------
namespace {
class VolumeDragTool final : public XsheetGUI::DragTool {
int m_index;
bool m_enabled;
public:
VolumeDragTool(XsheetViewer *viewer)
: DragTool(viewer)
, m_index(TApp::instance()->getCurrentColumn()->getColumnIndex())
, m_enabled(false) {
TXshColumn *column = viewer->getXsheet()->getColumn(m_index);
m_enabled = column != 0 && !column->isLocked();
TXshSoundColumn *soundColumn = column->getSoundColumn();
assert(soundColumn);
if (soundColumn->isPlaying()) {
viewer->update();
// soundColumn->stop();
}
}
void onClick(const QMouseEvent *) override {}
void onDrag(const QMouseEvent *event) override {
if (!m_enabled) return;
const Orientation *o = getViewer()->orientation();
QRect track = o->rect(PredefinedRect::VOLUME_TRACK);
NumberRange range = o->frameSide(track);
int frameAxis = o->frameAxis(event->pos());
if (o->isVerticalTimeline() &&
!o->flag(PredefinedFlag::VOLUME_AREA_VERTICAL)) {
range = o->layerSide(track);
frameAxis =
o->layerAxis(event->pos()) - getViewer()->columnToLayerAxis(m_index);
}
double v = range.ratio(frameAxis);
if (o->flag(PredefinedFlag::VOLUME_AREA_VERTICAL)) v = 1 - v;
TXsheet *xsh = getViewer()->getXsheet();
TXshColumn *column = xsh->getColumn(m_index);
if (!column) return;
TXshSoundColumn *soundColumn = column->getSoundColumn();
if (!soundColumn) return;
soundColumn->setVolume(v);
getViewer()->update();
}
void onRelease(const QMouseEvent *) override {
TApp::instance()->getCurrentXsheet()->notifyXsheetSoundChanged();
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeVolumeDragTool(
XsheetViewer *viewer) {
return new VolumeDragTool(viewer);
}
//=============================================================================
// SoundScrub tool
//-----------------------------------------------------------------------------
namespace {
class SoundScrubTool final : public XsheetGUI::DragTool {
TXshSoundColumn *m_soundColumn;
int m_startRow;
std::pair<int, int> m_playRange;
int m_row, m_oldRow;
int m_timerId;
public:
SoundScrubTool(XsheetViewer *viewer, TXshSoundColumn *sc)
: DragTool(viewer)
, m_soundColumn(sc)
, m_startRow(-1)
, m_playRange(0, -1)
, m_row(0)
, m_oldRow(0)
, m_timerId(0) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
TColumnSelection *selection = getViewer()->getColumnSelection();
selection->selectNone();
m_startRow = row;
getViewer()->setScrubHighlight(row, m_startRow, col);
getViewer()->updateCells();
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame(), col = pos.layer();
onCellChange(row, col);
}
void onCellChange(int row, int col) {
assert(m_startRow >= 0);
getViewer()->setScrubHighlight(row, m_startRow, col);
getViewer()->updateCells();
}
void onRelease(const CellPosition &pos) override {
int r0 = std::min(pos.frame(), m_startRow);
int r1 = std::max(pos.frame(), m_startRow);
assert(m_soundColumn);
TApp *app = TApp::instance();
ToonzScene *scene = app->getCurrentScene()->getScene();
double fps = scene->getProperties()->getOutputProperties()->getFrameRate();
app->getCurrentFrame()->scrubColumn(r0, r1, m_soundColumn, fps);
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeSoundScrubTool(
XsheetViewer *viewer, TXshSoundColumn *sc) {
return new SoundScrubTool(viewer, sc);
}
//=============================================================================
// DataDragTool
//-----------------------------------------------------------------------------
namespace {
//=============================================================================
// DragAndDropData
//-----------------------------------------------------------------------------
class DragAndDropData {
public:
std::vector<std::pair<TXshSimpleLevelP, std::vector<TFrameId>>> m_levels;
std::vector<TFilePath> m_paths;
DragAndDropData() {}
void addSimpleLevel(
std::pair<TXshSimpleLevelP, std::vector<TFrameId>> level) {
m_levels.push_back(level);
}
void setSimpleLevels(
std::vector<std::pair<TXshSimpleLevelP, std::vector<TFrameId>>> levels) {
m_levels = levels;
}
TRect getLevelFrameRect(bool isVertical) {
if (!m_paths.empty()) return TRect();
int maxRow = 0;
int columnCount = m_levels.size();
int i;
for (i = 0; i < columnCount; i++) {
std::vector<TFrameId> fids = m_levels[i].second;
int size = fids.size();
if (maxRow < size) maxRow = size;
}
if (!isVertical) return TRect(0, 0, maxRow - 1, columnCount - 1);
return TRect(0, 0, columnCount - 1, maxRow - 1);
}
void addPath(TFilePath path) { m_paths.push_back(path); }
void setLevelPath(std::vector<TFilePath> paths) { m_paths = paths; }
};
//=============================================================================
// DataDragTool
//-----------------------------------------------------------------------------
enum CellMovementType { NO_MOVEMENT, INSERT_CELLS, OVERWRITE_CELLS };
class DataDragTool final : public XsheetGUI::DragTool {
DragAndDropData *m_data;
bool m_valid;
TPoint m_curPos; // screen xy of drag begin
CellMovementType m_type;
protected:
bool canChange(int row, int col) {
int c = col;
int r = row;
TXsheet *xsh = getViewer()->getXsheet();
TRect rect = m_data->getLevelFrameRect(
getViewer()->orientation()->isVerticalTimeline());
for (c = col; c < rect.getLx() + col; c++) {
for (r = row; r < rect.getLy() + row; r++)
if (!xsh->getCell(r, c).isEmpty()) return false;
}
return true;
}
public:
DataDragTool(XsheetViewer *viewer)
: DragTool(viewer)
, m_data(new DragAndDropData())
, m_valid(false)
, m_type(NO_MOVEMENT) {}
void onClick(const QDropEvent *e) override {
if (e->mimeData()->hasUrls()) {
QList<QUrl> urls = e->mimeData()->urls();
int i;
for (i = 0; i < urls.size(); i++)
m_data->addPath(TFilePath(urls[i].toLocalFile().toStdWString()));
} else if (e->mimeData()->hasFormat(CastItems::getMimeFormat())) {
const CastItems *cast = dynamic_cast<const CastItems *>(e->mimeData());
int i;
for (i = 0; i < cast->getItemCount(); i++) {
CastItem *item = cast->getItem(i);
TXshSimpleLevel *sl = item->getSimpleLevel();
if (!sl) continue;
std::vector<TFrameId> fids;
sl->getFids(fids);
m_data->addSimpleLevel(std::make_pair(sl, fids));
}
} else if (e->mimeData()->hasFormat("application/vnd.toonz.drawings")) {
TFilmstripSelection *s =
dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
if (!s) return;
TXshSimpleLevel *sl =
TApp::instance()->getCurrentLevel()->getSimpleLevel();
if (!sl) return;
std::vector<TFrameId> fids;
std::set<TFrameId> fidsSet = s->getSelectedFids();
for (auto const &fid : fidsSet) {
fids.push_back(fid);
}
m_data->addSimpleLevel(std::make_pair(sl, fids));
}
refreshCellsArea();
}
void onDrag(const QDropEvent *e) override {
TPoint pos(e->pos().x(), e->pos().y());
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
m_valid = true;
if (e->keyboardModifiers() & Qt::ShiftModifier)
m_type = INSERT_CELLS;
else if (e->keyboardModifiers() & Qt::AltModifier)
m_type = OVERWRITE_CELLS;
else
m_valid = canChange(row, col);
m_curPos = pos;
refreshCellsArea();
}
void onRelease(const QDropEvent *e) override {
TPoint pos(e->pos().x(), e->pos().y());
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
if (m_type != NO_MOVEMENT && !m_valid) return;
bool insert = m_type == INSERT_CELLS;
bool overWrite = m_type == OVERWRITE_CELLS;
if (!m_data->m_paths
.empty()) // caso in cui ho i path e deve caricare i livelli
{
IoCmd::LoadResourceArguments args;
args.row0 = row;
args.col0 = col;
args.resourceDatas.assign(m_data->m_paths.begin(), m_data->m_paths.end());
IoCmd::loadResources(args);
} else if (!m_data->m_levels.empty()) {
int i;
for (i = 0; i < (int)m_data->m_levels.size(); i++) {
TXshSimpleLevel *sl = m_data->m_levels[i].first.getPointer();
std::vector<TFrameId> fids = m_data->m_levels[i].second;
if (!sl || fids.empty()) continue;
IoCmd::exposeLevel(sl, row, col, fids, insert, overWrite);
}
}
refreshCellsArea();
}
void drawCellsArea(QPainter &p) override {
const Orientation *o = getViewer()->orientation();
CellPosition beginDragPosition = getViewer()->xyToPosition(m_curPos);
TPoint pos(beginDragPosition.layer(),
beginDragPosition.frame()); // row and cell coordinates
bool isVertical = getViewer()->orientation()->isVerticalTimeline();
if (!isVertical) {
pos.x = beginDragPosition.frame();
pos.y = beginDragPosition.layer();
}
TRect rect =
m_data->getLevelFrameRect(isVertical); // row and cell coordinates
if (rect.isEmpty()) return;
rect += pos;
if (rect.x1 < 0 || rect.y1 < 0) return;
if (rect.x0 < 0) rect.x0 = 0;
if (rect.y0 < 0) rect.y0 = 0;
QRect screenCell;
if (o->isVerticalTimeline())
screenCell = getViewer()->rangeToXYRect(
CellRange(CellPosition(rect.y0, rect.x0),
CellPosition(rect.y1 + 1, rect.x1 + 1)));
else {
int newY0 = std::max(rect.y0, rect.y1);
int newY1 = std::min(rect.y0, rect.y1);
screenCell = getViewer()->rangeToXYRect(CellRange(
CellPosition(rect.x0, newY0), CellPosition(rect.x1 + 1, newY1 - 1)));
}
p.setPen(m_valid ? QColor(190, 220, 255) : QColor(255, 0, 0));
int i;
for (i = 0; i < 3; i++) // thick border within cell
p.drawRect(QRect(screenCell.topLeft() + QPoint(i, i),
screenCell.size() - QSize(2 * i, 2 * i)));
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeDragAndDropDataTool(
XsheetViewer *viewer) {
return new DataDragTool(viewer);
}
//=============================================================================
// NavigationTagDragTool
//-----------------------------------------------------------------------------
namespace {
class NavigationTagDragTool final : public XsheetGUI::DragTool {
int m_taggedRow;
public:
NavigationTagDragTool(XsheetViewer *viewer) : DragTool(viewer) {}
void onClick(const CellPosition &pos) override {
int row = pos.frame();
m_taggedRow = row;
refreshRowsArea();
}
void onDrag(const CellPosition &pos) override {
int row = pos.frame();
if (row < 0) row = 0;
onRowChange(row);
refreshRowsArea();
}
void onRowChange(int row) {
if (row < 0) return;
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
NavigationTags *navTags = xsh->getNavigationTags();
if (m_taggedRow == row || navTags->isTagged(row)) return;
navTags->moveTag(m_taggedRow, row);
m_taggedRow = row;
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
XsheetGUI::DragTool *XsheetGUI::DragTool::makeNavigationTagDragTool(
XsheetViewer *viewer) {
return new NavigationTagDragTool(viewer);
}