Blob Blame Raw


#include "keyframeselection.h"

// Tnz6 includes
#include "keyframedata.h"
#include "cellkeyframedata.h"
#include "tapp.h"
#include "menubarcommandids.h"

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

// TnzLib includes
#include "toonz/txsheethandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/stageobjectutil.h"
#include "toonz/txsheet.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/txshcolumn.h"

// TnzCore includes
#include "tundo.h"

// Qt includes
#include <QApplication>
#include <QClipboard>

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

struct PegbarArgument {
  TStageObject *m_stageObject;
  std::set<int> m_frames;
};

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

bool shiftKeyframesWithoutUndo(int r0, int r1, int c0, int c1, bool cut) {
  int delta = cut ? -(r1 - r0 + 1) : r1 - r0 + 1;
  if (delta == 0) return false;
  TXsheet *xsh   = TApp::instance()->getCurrentXsheet()->getXsheet();
  bool isShifted = false;
  int x;
  for (x = c0; x <= c1; x++) {
    TStageObject *stObj = xsh->getStageObject(
        x >= 0 ? TStageObjectId::ColumnId(x) : TStageObjectId::CameraId(0));
    std::set<int> keyToShift;
    int kr0, kr1;
    stObj->getKeyframeRange(kr0, kr1);
    int i = r0;
    while (i <= kr1) {
      if (stObj->isKeyframe(i)) keyToShift.insert(i);
      i++;
    }
    isShifted = stObj->moveKeyframes(keyToShift, delta);
  }
  return isShifted;
}

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

void copyKeyframesWithoutUndo(
    std::set<TKeyframeSelection::Position> *positions) {
  QClipboard *clipboard = QApplication::clipboard();
  TXsheet *xsh          = TApp::instance()->getCurrentXsheet()->getXsheet();
  TKeyframeData *data   = new TKeyframeData();
  data->setKeyframes(*positions, xsh);
  clipboard->setMimeData(data, QClipboard::Clipboard);
}

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

bool pasteKeyframesWithoutUndo(
    const TKeyframeData *data,
    std::set<TKeyframeSelection::Position> *positions) {
  if (!data || positions->empty()) return false;
  TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
  if (!data->getKeyframes(*positions, xsh)) return false;
  return true;
}

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

bool deleteKeyframesWithoutUndo(
    std::set<TKeyframeSelection::Position> *positions) {
  TApp *app = TApp::instance();
  assert(app);
  TXsheet *xsh            = app->getCurrentXsheet()->getXsheet();
  TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();

  if (positions->empty()) return false;

  std::set<TKeyframeSelection::Position>::iterator it = positions->begin();
  bool areAllColumnLocked                             = true;
  for (; it != positions->end(); ++it) {
    int row              = it->first;
    int col              = it->second;
    TStageObject *pegbar = xsh->getStageObject(
        col >= 0 ? TStageObjectId::ColumnId(col) : cameraId);
    if (pegbar->getId().isColumn() && xsh->getColumn(col) &&
        xsh->getColumn(col)->isLocked())
      continue;
    areAllColumnLocked = false;
    assert(pegbar);
    pegbar->removeKeyframeWithoutUndo(row);
  }
  if (areAllColumnLocked) return false;

  positions->clear();
  return true;
}

//=============================================================================
//  PasteKeyframesUndo
//-----------------------------------------------------------------------------

class PasteKeyframesUndo final : public TUndo {
  TKeyframeSelection *m_selection;
  QMimeData *m_newData;
  QMimeData *m_oldData;
  int m_r0, m_r1, m_c0, m_c1;

public:
  PasteKeyframesUndo(TKeyframeSelection *selection, QMimeData *newData,
                     QMimeData *oldData, int r0, int r1, int c0, int c1)
      : m_selection(selection)
      , m_newData(newData)
      , m_oldData(oldData)
      , m_r0(r0)
      , m_r1(r1)
      , m_c0(c0)
      , m_c1(c1) {}

  ~PasteKeyframesUndo() {
    delete m_selection;
    delete m_newData;
    delete m_oldData;
  }
  // data->xsh
  void setXshFromData(QMimeData *data) const {
    const TKeyframeData *keyframeData = dynamic_cast<TKeyframeData *>(data);
    if (keyframeData) {
      TKeyframeSelection *selection =
          new TKeyframeSelection(m_selection->getSelection());
      pasteKeyframesWithoutUndo(keyframeData, &selection->getSelection());
    }
  }
  void undo() const override {
    // Delete merged data
    TKeyframeSelection *selection =
        new TKeyframeSelection(m_selection->getSelection());
    deleteKeyframesWithoutUndo(&selection->getSelection());
    if (-(m_r1 - m_r0 + 1) != 0)
      shiftKeyframesWithoutUndo(m_r0, m_r1, m_c0, m_c1, true);
    if (m_oldData) setXshFromData(m_oldData);
    TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
  }
  void redo() const override {
    if (m_r1 - m_r0 + 1 != 0)
      shiftKeyframesWithoutUndo(m_r0, m_r1, m_c0, m_c1, false);
    // Delete merged data
    setXshFromData(m_newData);
    TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
  }
  int getSize() const override { return sizeof(*this); }

  QString getHistoryString() override {
    return QObject::tr("Paste Key Frames");
  }
};

//=============================================================================
//  DeleteKeyframesUndo
//-----------------------------------------------------------------------------

class DeleteKeyframesUndo final : public TUndo {
  TKeyframeSelection *m_selection;
  QMimeData *m_data;
  int m_r0, m_r1, m_c0, m_c1;

public:
  DeleteKeyframesUndo(TKeyframeSelection *selection, QMimeData *data, int r0,
                      int r1, int c0, int c1)
      : m_selection(selection)
      , m_data(data)
      , m_r0(r0)
      , m_r1(r1)
      , m_c0(c0)
      , m_c1(c1) {}

  ~DeleteKeyframesUndo() {
    delete m_selection;
    delete m_data;
  }

  void undo() const override {
    const TKeyframeData *keyframeData = dynamic_cast<TKeyframeData *>(m_data);
    if (m_r1 - m_r0 + 1 != 0)
      shiftKeyframesWithoutUndo(m_r0, m_r1, m_c0, m_c1, false);
    if (keyframeData)
      pasteKeyframesWithoutUndo(keyframeData, &m_selection->getSelection());
    TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
  }

  void redo() const override {
    deleteKeyframesWithoutUndo(&m_selection->getSelection());
    if (m_r1 - m_r0 + 1 != 0)
      shiftKeyframesWithoutUndo(m_r0, m_r1, m_c0, m_c1, true);
    TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
  }

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

  QString getHistoryString() override {
    return QObject::tr("Delete Key Frames");
  }
};

//-----------------------------------------------------------------------------
}  // namespace
//-----------------------------------------------------------------------------

//=============================================================================
// TKeyframeSelection
//-----------------------------------------------------------------------------

void TKeyframeSelection::enableCommands() {
  enableCommand(this, MI_Copy, &TKeyframeSelection::copyKeyframes);
  enableCommand(this, MI_Paste, &TKeyframeSelection::pasteKeyframes);
  enableCommand(this, MI_Cut, &TKeyframeSelection::cutKeyframes);
  enableCommand(this, MI_Clear, &TKeyframeSelection::deleteKeyframes);
}

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

int TKeyframeSelection::getFirstRow() const {
  if (isEmpty()) return 0;
  return m_positions.begin()->first;
}

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

void TKeyframeSelection::unselectLockedColumn() {
  TApp *app = TApp::instance();
  assert(app);
  TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
  std::set<Position> positions;
  std::set<Position>::iterator it;
  for (it = m_positions.begin(); it != m_positions.end(); ++it) {
    int col = it->second;
    if (col >= 0 && xsh->getColumn(col) && xsh->getColumn(col)->isLocked())
      continue;
    positions.insert(*it);
  }
  m_positions.swap(positions);
}

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

bool TKeyframeSelection::select(const TSelection *s) {
  if (const TKeyframeSelection *ss =
          dynamic_cast<const TKeyframeSelection *>(s)) {
    std::set<Position> pos(ss->m_positions);
    m_positions.swap(pos);
    makeCurrent();
    return true;
  } else
    return false;
}

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

void TKeyframeSelection::setKeyframes() {
  TApp *app                   = TApp::instance();
  TXsheetHandle *xsheetHandle = app->getCurrentXsheet();
  TXsheet *xsh                = xsheetHandle->getXsheet();
  TStageObjectId cameraId     = xsh->getStageObjectTree()->getCurrentCameraId();
  if (isEmpty()) return;
  Position pos         = *m_positions.begin();
  int row              = pos.first;
  int col              = pos.second;
  TStageObjectId id    = col < 0 ? cameraId : TStageObjectId::ColumnId(col);
  TStageObject *pegbar = xsh->getStageObject(id);
  if (!pegbar) return;
  if (pegbar->isKeyframe(row)) {
    TStageObject::Keyframe key = pegbar->getKeyframe(row);
    pegbar->removeKeyframeWithoutUndo(row);
    UndoRemoveKeyFrame *undo =
        new UndoRemoveKeyFrame(id, row, key, xsheetHandle);
    undo->setObjectHandle(app->getCurrentObject());
    TUndoManager::manager()->add(undo);
  } else {
    pegbar->setKeyframeWithoutUndo(row);
    UndoSetKeyFrame *undo = new UndoSetKeyFrame(id, row, xsheetHandle);
    undo->setObjectHandle(app->getCurrentObject());
    TUndoManager::manager()->add(undo);
  }
  TApp::instance()->getCurrentScene()->setDirtyFlag(true);
  TApp::instance()->getCurrentObject()->notifyObjectIdChanged(false);
}

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

void TKeyframeSelection::copyKeyframes() {
  if (isEmpty()) return;
  copyKeyframesWithoutUndo(&m_positions);
}

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

void TKeyframeSelection::pasteKeyframes() {
  pasteKeyframesWithShift(0, 0, -1, -1);
}

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

void TKeyframeSelection::deleteKeyframes() {
  deleteKeyframesWithShift(0, -1, 0, -1);
}

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

void TKeyframeSelection::cutKeyframes() {
  copyKeyframes();
  deleteKeyframesWithShift(0, -1, 0, -1);
}

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

void TKeyframeSelection::pasteKeyframesWithShift(int r0, int r1, int c0,
                                                 int c1) {
  unselectLockedColumn();

  // Retrieve keyframes to paste
  QClipboard *clipboard = QApplication::clipboard();
  const TKeyframeData *data =
      dynamic_cast<const TKeyframeData *>(clipboard->mimeData());
  if (!data) {
    const TCellKeyframeData *cellKeyframeData =
        dynamic_cast<const TCellKeyframeData *>(clipboard->mimeData());
    if (cellKeyframeData) data = cellKeyframeData->getKeyframeData();
  }
  if (!data) return;

  // Retrieve corresponding old keyframes
  std::set<TKeyframeSelection::Position> positions(m_positions);
  data->getKeyframes(positions);

  TKeyframeData *oldData = new TKeyframeData();
  TXsheet *xsh           = TApp::instance()->getCurrentXsheet()->getXsheet();
  oldData->setKeyframes(positions, xsh);

  bool isShift = shiftKeyframesWithoutUndo(r0, r1, c0, c1, false);
  bool isPaste = pasteKeyframesWithoutUndo(data, &m_positions);
  if (!isPaste && !isShift) {
    delete oldData;
    return;
  }

  TKeyframeData *newData = new TKeyframeData();
  newData->setKeyframes(m_positions, xsh);
  TKeyframeSelection *selection = new TKeyframeSelection(m_positions);
  TUndoManager::manager()->add(
      new PasteKeyframesUndo(selection, newData, oldData, r0, r1, c0, c1));
  TApp::instance()->getCurrentScene()->setDirtyFlag(true);
  TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}

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

void TKeyframeSelection::deleteKeyframesWithShift(int r0, int r1, int c0,
                                                  int c1) {
  unselectLockedColumn();

  TKeyframeData *data = new TKeyframeData();
  TXsheet *xsh        = TApp::instance()->getCurrentXsheet()->getXsheet();
  data->setKeyframes(m_positions, xsh);
  if (m_positions.empty()) {
    delete data;
    return;
  }
  TKeyframeSelection *selection = new TKeyframeSelection(m_positions);
  bool deleteKeyFrame           = deleteKeyframesWithoutUndo(&m_positions);
  bool isShift = shiftKeyframesWithoutUndo(r0, r1, c0, c1, true);
  if (!deleteKeyFrame && !isShift) {
    delete selection;
    delete data;
    return;
  }
  TUndoManager::manager()->add(
      new DeleteKeyframesUndo(selection, data, r0, r1, c0, c1));
  TApp::instance()->getCurrentScene()->setDirtyFlag(true);
  TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}