Blob Blame Raw


#include "xshcellviewer.h"

// Tnz6 includes
#include "xsheetviewer.h"
#include "tapp.h"
#include "menubarcommandids.h"
#include "cellselection.h"
#include "keyframeselection.h"
#include "filmstripselection.h"
#include "filmstripcommand.h"
#include "xsheetdragtool.h"
#include "iocommand.h"
#include "castselection.h"
#include "cellkeyframeselection.h"
#include "keyframedata.h"
#include "columnselection.h"

// TnzTools includes
#include "tools/cursormanager.h"
#include "tools/toolhandle.h"

// TnzQt includes
#include "toonzqt/gutil.h"
#include "toonzqt/tselectionhandle.h"
#include "historytypes.h"

// TnzLib includes
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/tframehandle.h"
#include "toonz/txshleveltypes.h"
#include "toonz/txsheet.h"
#include "toonz/toonzscene.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshsoundcolumn.h"
#include "toonz/txshcell.h"
#include "toonz/tstageobject.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/sceneproperties.h"
#include "toonz/columnfan.h"
#include "toonz/levelset.h"
#include "toonz/levelproperties.h"
#include "toonz/txshsoundlevel.h"
#include "toonz/txshnoteset.h"
#include "toonz/txshcolumn.h"
#include "toonz/tcolumnfx.h"
#include "toonz/txshchildlevel.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/txshsoundtextlevel.h"
#include "toonz/txshpalettelevel.h"
#include "toonz/doubleparamcmd.h"
#include "toonz/preferences.h"

// TnzBase includes
#include "tdoublekeyframe.h"

// TnzCore includes
#include "tconvert.h"
#include "tundo.h"

// Qt includes
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#include <QUrl>
#include <QToolTip>
#include <QApplication>
#include <QClipboard>

namespace {

bool selectionContainTlvImage(TCellSelection *selection, TXsheet *xsheet,
                              bool onlyTlv = false) {
  int r0, r1, c0, c1;
  selection->getSelectedCells(r0, c0, r1, c1);

  // Verifico se almeno un livello della selezione e' un tlv
  int c, r;
  for (c = c0; c <= c1; c++)
    for (r = r0; r <= r1; r++) {
      TXshCell cell = xsheet->getCell(r, c);
      TXshSimpleLevel *level =
          cell.isEmpty() ? (TXshSimpleLevel *)0 : cell.getSimpleLevel();
      if (!level) continue;

      if (level->getType() != TZP_XSHLEVEL && onlyTlv) return false;
      if (level->getType() == TZP_XSHLEVEL && !onlyTlv) return true;
    }

  return false;
}

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

bool selectionContainRasterImage(TCellSelection *selection, TXsheet *xsheet,
                                 bool onlyRaster = false) {
  int r0, r1, c0, c1;
  selection->getSelectedCells(r0, c0, r1, c1);

  // Verifico se almeno un livello della selezione e' un tlv
  int c, r;
  for (c = c0; c <= c1; c++)
    for (r = r0; r <= r1; r++) {
      TXshCell cell = xsheet->getCell(r, c);
      TXshSimpleLevel *level =
          cell.isEmpty() ? (TXshSimpleLevel *)0 : cell.getSimpleLevel();
      if (!level) continue;

      if (level->getType() != OVL_XSHLEVEL && onlyRaster) return false;
      if (level->getType() == OVL_XSHLEVEL && !onlyRaster) return true;
    }

  return false;
}

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

bool selectionContainLevelImage(TCellSelection *selection, TXsheet *xsheet) {
  int r0, r1, c0, c1;
  selection->getSelectedCells(r0, c0, r1, c1);
  // Verifico se almeno un livello della selezione e' un tlv, un pli o un
  // fullcolor (!= .psd)
  int c, r;
  for (c = c0; c <= c1; c++)
    for (r = r0; r <= r1; r++) {
      TXshCell cell = xsheet->getCell(r, c);
      TXshSimpleLevel *level =
          cell.isEmpty() ? (TXshSimpleLevel *)0 : cell.getSimpleLevel();
      if (!level) continue;

      std::string ext = level->getPath().getType();
      int type        = level->getType();
      if (type == TZP_XSHLEVEL || type == PLI_XSHLEVEL ||
          (type == OVL_XSHLEVEL && ext != "psd"))
        return true;
    }

  return false;
}

//-----------------------------------------------------------------------------
/*! convert the last one digit of the frame number to alphabet
    Ex.  12 -> 1B    21 -> 2A   30 -> 3
*/
void parse_with_letter(const QString &text, std::wstring &levelName,
                       TFrameId &fid) {
  QRegExp spaces("\\t|\\s");
  QRegExp numbers("\\d+");
  QRegExp caracters("[^\\d+]");
  QString str = text;

  // remove final spaces
  int size = str.size();
  while (str.lastIndexOf(spaces) == size - 1 && size > 0)
    str.remove(str.size() - 1, 1);
  if (str.isEmpty()) {
    levelName = L"";
    fid       = TFrameId::NO_FRAME;
    return;
  }

  int lastSpaceIndex = str.lastIndexOf(spaces);
  // if input only digits / alphabet
  if (lastSpaceIndex == -1) {
    // in case of only level name
    if (str.contains(caracters) && !str.contains(numbers)) {
      levelName = text.toStdWString();
      fid       = TFrameId::NO_FRAME;
    }
    // in case of input frame number + alphabet
    else if (str.contains(numbers) && str.contains(caracters)) {
      levelName = L"";

      QString appendix = str.right(1);

      int appendNum = 0;

      if (appendix == QString('A') || appendix == QString('a'))
        appendNum = 1;
      else if (appendix == QString('B') || appendix == QString('b'))
        appendNum = 2;
      else if (appendix == QString('C') || appendix == QString('c'))
        appendNum = 3;
      else if (appendix == QString('D') || appendix == QString('d'))
        appendNum = 4;
      else if (appendix == QString('E') || appendix == QString('e'))
        appendNum = 5;
      else if (appendix == QString('F') || appendix == QString('f'))
        appendNum = 6;
      else if (appendix == QString('G') || appendix == QString('g'))
        appendNum = 7;
      else if (appendix == QString('H') || appendix == QString('h'))
        appendNum = 8;
      else if (appendix == QString('I') || appendix == QString('i'))
        appendNum = 9;

      str.chop(1);

      if (str.contains(caracters) || !str.contains(numbers)) {
        levelName = text.toStdWString();
        fid       = TFrameId::NO_FRAME;
        return;
      }

      fid = TFrameId(str.toInt() * 10 + appendNum);
    }
    // in case of input only frame number
    else if (str.contains(numbers)) {
      levelName = L"";
      fid       = TFrameId(str.toInt() * 10);
    }
  }
  // if input both the level name and frame number
  else {
    QString lastString = str.right(str.size() - 1 - lastSpaceIndex);
    if (lastString.contains(numbers)) {
      // level name
      QString firstString = str.left(lastSpaceIndex);
      levelName           = firstString.toStdWString();

      // frame number + alphabet
      if (lastString.contains(caracters)) {
        QString appendix = lastString.right(1);

        int appendNum = 0;

        if (appendix == QString('A') || appendix == QString('a'))
          appendNum = 1;
        else if (appendix == QString('B') || appendix == QString('b'))
          appendNum = 2;
        else if (appendix == QString('C') || appendix == QString('c'))
          appendNum = 3;
        else if (appendix == QString('D') || appendix == QString('d'))
          appendNum = 4;
        else if (appendix == QString('E') || appendix == QString('e'))
          appendNum = 5;
        else if (appendix == QString('F') || appendix == QString('f'))
          appendNum = 6;
        else if (appendix == QString('G') || appendix == QString('g'))
          appendNum = 7;
        else if (appendix == QString('H') || appendix == QString('h'))
          appendNum = 8;
        else if (appendix == QString('I') || appendix == QString('i'))
          appendNum = 9;

        lastString.chop(1);

        if (lastString.contains(caracters) || !lastString.contains(numbers)) {
          levelName = text.toStdWString();
          fid       = TFrameId::NO_FRAME;
          return;
        }

        fid = TFrameId(lastString.toInt() * 10 + appendNum);
      }
      // only frame number
      else if (lastString.contains(numbers)) {
        fid = TFrameId(lastString.toInt() * 10);
      }

    } else {
      levelName = text.toStdWString();
      fid       = TFrameId::NO_FRAME;
    }
  }
}

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

void parse(const QString &text, std::wstring &levelName, TFrameId &fid) {
  QRegExp spaces("\\t|\\s");
  QRegExp numbers("\\d+");
  QRegExp caracters("[^\\d+]");
  QString str = text;

  // remove final spaces
  int size = str.size();
  while (str.lastIndexOf(spaces) == size - 1 && size > 0)
    str.remove(str.size() - 1, 1);
  if (str.isEmpty()) {
    levelName = L"";
    fid       = TFrameId::NO_FRAME;
    return;
  }
  int lastSpaceIndex = str.lastIndexOf(spaces);
  if (lastSpaceIndex == -1) {
    if (str.contains(numbers) && !str.contains(caracters)) {
      levelName = L"";
      fid       = TFrameId(str.toInt());
    } else if (str.contains(caracters)) {
      levelName = text.toStdWString();
      fid       = TFrameId::NO_FRAME;
    }
  } else {
    QString lastString = str.right(str.size() - 1 - lastSpaceIndex);
    if (lastString.contains(numbers) && !lastString.contains(caracters)) {
      QString firstString = str.left(lastSpaceIndex);
      levelName           = firstString.toStdWString();
      fid                 = TFrameId(lastString.toInt());
    } else if (lastString.contains(caracters)) {
      levelName = text.toStdWString();
      fid       = TFrameId::NO_FRAME;
    }
  }
}

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

bool isGlobalKeyFrameWithSameTypeDiffFromLinear(TStageObject *stageObject,
                                                int frame) {
  if (!stageObject->isFullKeyframe(frame)) return false;
  TDoubleKeyframe::Type type =
      stageObject->getParam(TStageObject::T_Angle)->getKeyframeAt(frame).m_type;
  if (type == TDoubleKeyframe::Linear) return false;
  if (type !=
          stageObject->getParam(TStageObject::T_X)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_Y)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_Z)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_SO)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_ScaleX)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_ScaleY)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_Scale)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_Path)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_ShearX)
              ->getKeyframeAt(frame)
              .m_type ||
      type !=
          stageObject->getParam(TStageObject::T_ShearY)
              ->getKeyframeAt(frame)
              .m_type)
    return false;
  return true;
}

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

bool rectContainsPos(QList<QRect> rects, QPoint pos) {
  int i;
  for (i = 0; i < rects.size(); i++)
    if (rects.at(i).contains(pos)) return true;
  return false;
}

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

bool isGlobalKeyFrameWithSamePrevTypeDiffFromLinear(TStageObject *stageObject,
                                                    int frame) {
  if (!stageObject->isFullKeyframe(frame)) return false;
  TDoubleKeyframe::Type type = stageObject->getParam(TStageObject::T_Angle)
                                   ->getKeyframeAt(frame)
                                   .m_prevType;
  if (type == TDoubleKeyframe::Linear) return false;
  if (type !=
          stageObject->getParam(TStageObject::T_X)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_Y)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_Z)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_SO)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_ScaleX)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_ScaleY)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_Scale)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_Path)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_ShearX)
              ->getKeyframeAt(frame)
              .m_prevType ||
      type !=
          stageObject->getParam(TStageObject::T_ShearY)
              ->getKeyframeAt(frame)
              .m_prevType)
    return false;
  return true;
}

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

#ifdef LINETEST

int getParamStep(TStageObject *stageObject, int frame) {
  TDoubleKeyframe keyFrame =
      stageObject->getParam(TStageObject::T_Angle)->getKeyframeAt(frame);
  return keyFrame.m_step;
}

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

void setParamStep(int indexKeyframe, int step, TDoubleParam *param) {
  KeyframeSetter setter(param, indexKeyframe);
  setter.setStep(step);
}

#endif
//=============================================================================
// RenameCellUndo
//-----------------------------------------------------------------------------

class RenameCellUndo : public TUndo {
  int m_row;
  int m_col;
  const TXshCell m_oldCell;
  const TXshCell m_newCell;

public:
  // indices sono le colonne inserite
  RenameCellUndo(int row, int col, TXshCell oldCell, TXshCell newCell)
      : m_row(row), m_col(col), m_oldCell(oldCell), m_newCell(newCell) {}

  void setcell(const TXshCell cell) const {
    TApp *app    = TApp::instance();
    TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
    assert(xsh);
    if (cell.isEmpty())
      xsh->clearCells(m_row, m_col);
    else
      xsh->setCell(m_row, m_col, cell);
    app->getCurrentXsheet()->notifyXsheetChanged();
  }

  void undo() const { setcell(m_oldCell); }

  void redo() const { setcell(m_newCell); }

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

  QString getHistoryString() {
    return QObject::tr("Rename Cell  at Column %1  Frame %2")
        .arg(QString::number(m_col + 1))
        .arg(QString::number(m_row + 1));
  }
  int getHistoryType() { return HistoryType::Xsheet; }
};

// display upper-directional smart tab only when pressing ctrl key
bool isCtrlPressed = false;

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

namespace XsheetGUI {

//=============================================================================
// RenameCellField
//-----------------------------------------------------------------------------

RenameCellField::RenameCellField(QWidget *parent, XsheetViewer *viewer)
    : QLineEdit(parent), m_viewer(viewer), m_row(-1), m_col(-1) {
  setFixedSize(XsheetGUI::ColumnWidth + 3, XsheetGUI::RowHeight + 4);
  connect(this, SIGNAL(returnPressed()), SLOT(onReturnPressed()));
  setContextMenuPolicy(Qt::PreventContextMenu);
}

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

void RenameCellField::showInRowCol(int row, int col) {
  m_viewer->scrollTo(row, col);

  m_row = row;
  m_col = col;

  move(QPoint(m_viewer->columnToX(col) - 1, m_viewer->rowToY(row) - 2));
  static QFont font("Helvetica", XSHEET_FONT_SIZE, QFont::Normal);
  setFont(font);

  // Se la cella non e' vuota setto il testo
  TXsheet *xsh  = m_viewer->getXsheet();
  TXshCell cell = xsh->getCell(row, col);
  if (!cell.isEmpty()) {
    TFrameId fid           = cell.getFrameId();
    std::wstring levelName = cell.m_level->getName();

    // convert the last one digit of the frame number to alphabet
    // Ex.  12 -> 1B    21 -> 2A   30 -> 3
    if (Preferences::instance()->isShowFrameNumberWithLettersEnabled())
      setText((fid.isEmptyFrame() || fid.isNoFrame())
                  ? QString::fromStdWString(levelName)
                  : QString::fromStdWString(levelName) + QString(" ") +
                        m_viewer->getFrameNumberWithLetters(fid.getNumber()));
    else {
      std::string frameNumber("");
      if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber());
      if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter());
      setText((frameNumber.empty())
                  ? QString::fromStdWString(levelName)
                  : QString::fromStdWString(levelName) + QString(" ") +
                        QString::fromStdString(frameNumber));
    }
    selectAll();
  }
  // clear the field if the empty cell is clicked
  else {
    setText("");
  }
  show();
  raise();
  setFocus();
}

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

void RenameCellField::renameCell() {
  QString s            = text();
  std::wstring newName = s.toStdWString();

  setText("");

  std::wstring levelName;
  TFrameId fid;

  // convert the last one digit of the frame number to alphabet
  // Ex.  12 -> 1B    21 -> 2A   30 -> 3
  if (Preferences::instance()->isShowFrameNumberWithLettersEnabled())
    parse_with_letter(QString::fromStdWString(newName), levelName, fid);
  else
    parse(QString::fromStdWString(newName), levelName, fid);
  TXshLevel *xl   = 0;
  TXsheet *xsheet = m_viewer->getXsheet();

  bool animationSheetEnabled =
      Preferences::instance()->isAnimationSheetEnabled();
  bool levelDefined =
      xsheet->getCell(m_row, m_col).getSimpleLevel() != 0 ||
      m_row > 0 && xsheet->getCell(m_row - 1, m_col).getSimpleLevel() != 0;

  if (animationSheetEnabled && levelDefined) {
    TXshCell cell       = xsheet->getCell(m_row, m_col);
    TXshSimpleLevel *sl = cell.getSimpleLevel();
    if (sl) {
      QRegExp fidRe("([0-9]+)([a-z]?)");
      if (fidRe.exactMatch(s)) {
#if QT_VERSION >= 0x050500
        fid = TFrameId(fidRe.cap(1).toInt(),
                       fidRe.cap(2) == "" ? 0 : fidRe.cap(2).toLatin1()[0]);
#else
        fid = TFrameId(fidRe.cap(1).toInt(),
                       fidRe.cap(2) == "" ? 0 : fidRe.cap(2).toAscii()[0]);
#endif
        FilmstripCmd::renumberDrawing(sl, cell.m_frameId, fid);
      }
    }
    return;
  }

  if (levelName == L"") {
    // prendo il livello dalla cella precedente. Se non c'e' dalla corrente
    // (forse sto modificando una cella non vuota)

    // no: faccio il contrario. cfr #6152. celle A-1,B-1. Edito B-1 e la
    // rinomino in 2. Quindi devo prima verificare
    // che la cella corrente non sia vuota

    TXshCell cell = xsheet->getCell(m_row, m_col);
    if (cell.isEmpty() && m_row > 0) {
      cell = xsheet->getCell(m_row - 1, m_col);
    }
    xl = cell.m_level.getPointer();
    if (!xl || (xl->getType() == OVL_XSHLEVEL &&
                xl->getPath().getFrame() == TFrameId::NO_FRAME))
      return;
    if (fid == TFrameId::NO_FRAME) fid = cell.m_frameId;
  } else {
    ToonzScene *scene   = m_viewer->getXsheet()->getScene();
    TLevelSet *levelSet = scene->getLevelSet();
    xl                  = levelSet->getLevel(levelName);
    if (!xl && fid != TFrameId::NO_FRAME) {
      if (animationSheetEnabled) {
        Preferences *pref   = Preferences::instance();
        int levelType       = pref->getDefLevelType();
        xl                  = scene->createNewLevel(levelType, levelName);
        TXshSimpleLevel *sl = xl->getSimpleLevel();
        if (levelType == TZP_XSHLEVEL || levelType == OVL_XSHLEVEL)
          sl->setFrame(fid, sl->createEmptyFrame());
      } else
        xl = scene->createNewLevel(TZI_XSHLEVEL, levelName);
    }
  }
  if (!xl) return;
  TXshCell cell(xl, fid);

  // register undo only if the cell is modified
  if (cell == xsheet->getCell(m_row, m_col)) return;

  RenameCellUndo *undo =
      new RenameCellUndo(m_row, m_col, xsheet->getCell(m_row, m_col), cell);
  xsheet->setCell(m_row, m_col, cell);
  TUndoManager::manager()->add(undo);
  TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
  TApp *app = TApp::instance();
  if (m_row == app->getCurrentFrame()->getFrame()) {
    app->getCurrentTool()->onImageChanged(
        (TImage::Type)app->getCurrentImageType());
    xsheet->getStageObjectTree()->invalidateAll();
  }
}

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

void RenameCellField::onReturnPressed() {
  renameCell();
  showInRowCol(m_row + 1, m_col);
}

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

void RenameCellField::focusOutEvent(QFocusEvent *e) {
  hide();

  QLineEdit::focusOutEvent(e);
}

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

void RenameCellField::keyPressEvent(QKeyEvent *event) {
  if (event->key() == Qt::Key_Up && m_row > 0) {
    renameCell();
    showInRowCol(m_row - 1, m_col);
  } else if (event->key() == Qt::Key_Down) {
    renameCell();
    showInRowCol(m_row + 1, m_col);
  }
  QLineEdit::keyPressEvent(event);
}

//=============================================================================
// CellArea
//-----------------------------------------------------------------------------

#if QT_VERSION >= 0x050500
CellArea::CellArea(XsheetViewer *parent, Qt::WindowFlags flags)
#else
CellArea::CellArea(XsheetViewer *parent, Qt::WFlags flags)
#endif
    : QWidget(parent, flags)
    , m_viewer(parent)
    , m_levelExtenderRect()
    , m_soundLevelModifyRects()
    , m_isPanning(false)
    , m_isMousePressed(false)
    , m_pos(-1, -1)
    , m_tooltip(tr(""))
    , m_renameCell(new RenameCellField(this, m_viewer)) {
  setAcceptDrops(true);
  setMouseTracking(true);
  m_renameCell->hide();
  setFocusPolicy(Qt::NoFocus);
}

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

CellArea::~CellArea() {}

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

DragTool *CellArea::getDragTool() const { return m_viewer->getDragTool(); }
void CellArea::setDragTool(DragTool *dragTool) {
  m_viewer->setDragTool(dragTool);
}

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

void CellArea::drawCells(QPainter &p, const QRect toBeUpdated) {
  TXsheet *xsh         = m_viewer->getXsheet();
  ColumnFan *columnFan = xsh->getColumnFan();

  TCellSelection *cellSelection = m_viewer->getCellSelection();
  int rS0, cS0, rS1, cS1;
  if (!cellSelection->isEmpty())
    cellSelection->getSelectedCells(rS0, cS0, rS1, cS1);

  int r0, r1, c0, c1;  // range of visible rows and columns
  r0 = m_viewer->yToRow(toBeUpdated.top());
  r1 = m_viewer->yToRow(toBeUpdated.bottom());
  c0 = m_viewer->xToColumn(toBeUpdated.left());
  c1 = m_viewer->xToColumn(toBeUpdated.right());

  // Sfondo bianco se row < xsh->getFrameCount()
  int xshRowCount = xsh->getFrameCount();

  if (xshRowCount > 0) {
    int filledCol;
    for (filledCol = xsh->getColumnCount() - 1; filledCol >= 0; filledCol--) {
      TXshColumn *currentColumn = xsh->getColumn(filledCol);
      if (!currentColumn) continue;
      if (currentColumn->isEmpty() == false) {
        p.fillRect(1, 0, m_viewer->columnToX(filledCol + 1),
                   m_viewer->rowToY(xshRowCount),
                   QBrush(m_viewer->getNotEmptyColumnColor()));
        break;
      }
    }
  }
  int xS0, xS1, yS0, yS1;

  if (!cellSelection->isEmpty()) {
    xS0        = m_viewer->columnToX(cS0);
    xS1        = m_viewer->columnToX(cS1 + 1) - 1;
    yS0        = m_viewer->rowToY(rS0);
    yS1        = m_viewer->rowToY(rS1 + 1) - 1;
    QRect rect = QRect(xS0, yS0, xS1 - xS0 + 1, yS1 - yS0 + 1);
    p.fillRect(rect, QBrush(m_viewer->getSelectedEmptyCellColor()));
  }

  // marker interval
  int distance, offset;
  TApp::instance()->getCurrentScene()->getScene()->getProperties()->getMarkers(
      distance, offset);
  if (distance == 0) distance = 6;

  int currentRow = m_viewer->getCurrentRow();
  int col, row;

  int x0 = std::max(1, toBeUpdated.left());
  int x1 = std::min(width(), toBeUpdated.right());

  int y0 = std::max(1, toBeUpdated.top());
  int y1 = std::min(height() - 2, toBeUpdated.bottom());
  m_soundLevelModifyRects.clear();
  for (col = c0; col <= c1; col++) {
    int x = m_viewer->columnToX(col);
    // Disegno la colonna
    if (!columnFan->isActive(col)) {
      p.fillRect(x + 1, y0, 2, y1 - y0 + 1, QBrush(Qt::white));
      p.fillRect(x + 4, y0, 2, y1 - y0 + 1, QBrush(Qt::white));
      p.fillRect(x + 7, y0, 2, y1 - y0 + 1, QBrush(Qt::white));
      p.setPen(m_viewer->getLightLineColor());
      p.drawLine(x, y0, x, y1);
      p.drawLine(x + 3, y0, x + 3, y1);
      p.drawLine(x + 6, y0, x + 6, y1);
      continue;
    }

    TXshColumn *column     = xsh->getColumn(col);
    bool isColumn          = (column) ? true : false;
    bool isSoundColumn     = false;
    bool isPaletteColumn   = false;
    bool isSoundTextColumn = false;
    if (isColumn) {
      isSoundColumn     = column->getSoundColumn() != 0;
      isPaletteColumn   = column->getPaletteColumn() != 0;
      isSoundTextColumn = column->getSoundTextColumn() != 0;
    }
    // check if the column is reference
    bool isReference = true;
    if (column) {  // Verifico se la colonna e' una mask
      if (column->isControl() || column->isRendered() ||
          column->getMeshColumn())
        isReference = false;
    }

    x0 = x + 1;
    x1 = m_viewer->columnToX(col + 1);

    // draw vertical lines
    p.setPen(Qt::black);
    if (x > 0) p.drawLine(x, y0, x, y1);
    p.setPen(Qt::white);
    if (x > 1) p.drawLine(x - 1, y0, x - 1, y1);

    for (row = r0; row <= r1; row++) {
      // draw horizontal lines
      QColor color = ((row - offset) % distance != 0)
                         ? m_viewer->getLightLineColor()
                         : m_viewer->getMarkerLineColor();

      p.setPen(color);
      int y = m_viewer->rowToY(row);
      p.drawLine(x0, y, x1, y);
      if (!isColumn) continue;
      // Disegno le celle a seconda del tipo di colonna
      if (isSoundColumn)
        drawSoundCell(p, row, col);
      else if (isPaletteColumn)
        drawPaletteCell(p, row, col, isReference);
      else if (isSoundTextColumn)
        drawSoundTextCell(p, row, col);
      else
        drawLevelCell(p, row, col, isReference);
    }

    // hide top-most interval line
    p.setPen(m_viewer->getLightLineColor());
    p.drawLine(x - 1, 0, x1 - 1, 0);
  }

  // smart tab
  // initialize the rectangles first
  m_levelExtenderRect      = QRect();
  m_upperLevelExtenderRect = QRect();
  int smartTabPosOffset =
      (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled()) ? 31
                                                                          : 20;
  if (!cellSelection->isEmpty() && !m_viewer->areSoundCellsSelected()) {
    m_levelExtenderRect = QRect(xS1 - smartTabPosOffset, yS1 + 1, 19, 8);
    p.setPen(Qt::black);
    p.setBrush(SmartTabColor);
    p.drawRoundRect(m_levelExtenderRect, 30, 75);
    QColor color = ((rS1 + 1 - offset) % distance != 0)
                       ? m_viewer->getLightLineColor()
                       : m_viewer->getMarkerLineColor();
    p.setPen(color);
    p.drawLine(xS1 - smartTabPosOffset, yS1 + 1, xS1 - 1, yS1 + 1);

    // upper-directional smart tab
    if (isCtrlPressed && rS0 > 0 && !m_viewer->areCellsSelectedEmpty()) {
      m_upperLevelExtenderRect = QRect(xS1 - smartTabPosOffset, yS0 - 8, 19, 8);
      p.setPen(Qt::black);
      p.setBrush(SmartTabColor);
      p.drawRoundRect(m_upperLevelExtenderRect, 30, 75);
      QColor color = ((rS0 - offset) % distance != 0)
                         ? m_viewer->getLightLineColor()
                         : m_viewer->getMarkerLineColor();
      p.setPen(color);
      p.drawLine(xS1 - smartTabPosOffset, yS0, xS1 - 1, yS0);
    }

    p.setBrush(Qt::NoBrush);
  }
}

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

void CellArea::drawSoundCell(QPainter &p, int row, int col) {
  TXshSoundColumn *soundColumn =
      m_viewer->getXsheet()->getColumn(col)->getSoundColumn();
  int x           = m_viewer->columnToX(col);
  int y           = m_viewer->rowToY(row);
  QRect rect      = QRect(x + 1, y, ColumnWidth - 1, RowHeight);
  int maxNumFrame = soundColumn->getMaxFrame() + 1;
  int startFrame  = soundColumn->getFirstRow();
  TXshCell cell   = soundColumn->getCell(row);
  if (soundColumn->isCellEmpty(row) || cell.isEmpty() || row > maxNumFrame ||
      row < startFrame)
    return;

  TXshSoundLevelP soundLevel = cell.getSoundLevel();

  int r0, r1;
  if (!soundColumn->getLevelRange(row, r0, r1)) return;
  bool isFirstRow = (row == r0);
  bool isLastRow  = (row == r1);

  TCellSelection *cellSelection     = m_viewer->getCellSelection();
  TColumnSelection *columnSelection = m_viewer->getColumnSelection();
  bool isSelected                   = cellSelection->isCellSelected(row, col) ||
                    columnSelection->isColumnSelected(col);

  // sfondo celle
  QRect backgroundRect = QRect(x + 1, y + 1, ColumnWidth - 1, RowHeight - 1);
  p.fillRect(backgroundRect, QBrush((isSelected) ? SelectedSoundColumnColor
                                                 : SoundColumnColor));
  if (isLastRow) {
    QPainterPath path(QPointF(x, y));
    path.lineTo(QPointF(x + 6, y));
    path.lineTo(QPointF(x + 6, y + 2));
    path.lineTo(QPointF(x, y + RowHeight - 2));
    p.fillPath(path, QBrush(SoundColumnBorderColor));
  } else
    p.fillRect(QRect(x, y, 6, RowHeight), QBrush(SoundColumnBorderColor));

  int x1    = rect.x() + 5;
  int x2    = rect.x() + rect.width();
  int x1Bis = x2 - 6;

  int offset  = row - cell.getFrameId().getNumber();
  int y0      = rect.y();
  int y1      = rect.bottomLeft().y();
  int soundY0 = y0 - m_viewer->rowToY(offset);

  int trackWidth = (x1Bis - x1) / 2;
  int lastMin, lastMax;
  DoublePair minmax;
  soundLevel->getValueAtPixel(soundY0, minmax);

  double pmin = minmax.first + trackWidth / 2.;
  double pmax = minmax.second + trackWidth / 2.;

  int delta = x1 + trackWidth / 2;
  lastMin   = tcrop((int)pmin, 0, trackWidth / 2) + delta;
  lastMax   = tcrop((int)pmax, trackWidth / 2, trackWidth - 1) + delta;

  bool scrub = m_viewer->isScrubHighlighted(row, col);

  int i;
  for (i = y0; i <= y1; i++) {
    DoublePair minmax;
    soundLevel->getValueAtPixel(soundY0, minmax);
    soundY0++;
    int min, max;
    double pmin = minmax.first + trackWidth / 2.;
    double pmax = minmax.second + trackWidth / 2.;

    int delta = x1 + trackWidth / 2;
    min       = tcrop((int)pmin, 0, trackWidth / 2) + delta;
    max       = tcrop((int)pmax, trackWidth / 2, trackWidth - 1) + delta;

    if (i != y0 || !isFirstRow) {
      // trattini a destra della colonna
      if (i % 2) {
        p.setPen((isSelected) ? SelectedSoundColumnColor : SoundColumnColor);
        p.drawLine(x1, i, x1Bis, i);
      } else {
        p.setPen(SoundColumnTrackColor);
        p.drawLine(x1Bis + 1, i, x2, i);
      }
    }

    if (scrub && i % 2) {
      p.setPen(SoundColumnHlColor);
      p.drawLine(x1Bis + 1, i, x2, i);
    }

    if (i != y0) {
      // "traccia audio" al centro della colonna
      p.setPen(SoundColumnTrackColor);
      p.drawLine(lastMin, i, min, i);
      p.drawLine(lastMax, i, max, i);
    }

    lastMin = min;
    lastMax = max;
  }

  p.setPen(SoundColumnExtenderColor);
  int r0WithoutOff, r1WithoutOff;
  bool ret =
      soundColumn->getLevelRangeWithoutOffset(row, r0WithoutOff, r1WithoutOff);
  assert(ret);

  if (isFirstRow) {
    if (r0 != r0WithoutOff) {
      p.drawLine(x1, y0 + 1, x2, y0 + 1);
      p.drawLine(x1, y0 + 2, x2, y0 + 2);
    }
    QRect modifierRect(x1, y0 + 1, XsheetGUI::ColumnWidth, 2);
    m_soundLevelModifyRects.append(modifierRect);
  }
  if (isLastRow) {
    if (r1 != r1WithoutOff) {
      p.drawLine(x, y1, x2, y1);
      p.drawLine(x, y1 - 1, x2, y1 - 1);
    }
    QRect modifierRect(x1, y1 - 1, XsheetGUI::ColumnWidth, 2);
    m_soundLevelModifyRects.append(modifierRect);
  }
}

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

void CellArea::drawLevelCell(QPainter &p, int row, int col, bool isReference) {
  TXsheet *xsh  = m_viewer->getXsheet();
  TXshCell cell = xsh->getCell(row, col);
  TXshCell prevCell;

  TCellSelection *cellSelection     = m_viewer->getCellSelection();
  TColumnSelection *columnSelection = m_viewer->getColumnSelection();
  bool isSelected                   = cellSelection->isCellSelected(row, col) ||
                    columnSelection->isColumnSelected(col);

  if (row > 0) prevCell = xsh->getCell(row - 1, col);
  // nothing to draw
  if (cell.isEmpty() && prevCell.isEmpty()) return;
  TXshCell nextCell;
  nextCell = xsh->getCell(row + 1, col);

  int x      = m_viewer->columnToX(col);
  int y      = m_viewer->rowToY(row);
  QRect rect = QRect(x + 1, y + 1, ColumnWidth - 1, RowHeight - 1);
  if (cell.isEmpty()) {  // vuol dire che la precedente non e' vuota
    QColor levelEndColor = m_viewer->getTextColor();
    levelEndColor.setAlphaF(0.3);
    p.setPen(levelEndColor);
    p.drawLine(rect.topLeft(), rect.bottomRight());
    p.drawLine(rect.topRight(), rect.bottomLeft());
    return;
  }

  // get cell colors
  QColor cellColor, sideColor;
  if (isReference) {
    cellColor = (isSelected) ? m_viewer->getSelectedReferenceColumnColor()
                             : m_viewer->getReferenceColumnColor();
    sideColor = m_viewer->getReferenceColumnBorderColor();
  } else {
    int levelType;
    m_viewer->getCellTypeAndColors(levelType, cellColor, sideColor, cell,
                                   isSelected);
  }

  // check if the level is scanned but not cleanupped
  bool yetToCleanupCell = false;
  if (!cell.isEmpty() && cell.getSimpleLevel()) {
    int frameStatus = cell.getSimpleLevel()->getFrameStatus(cell.m_frameId);
    const int mask  = TXshSimpleLevel::Cleanupped | TXshSimpleLevel::Scanned;
    const int yetToCleanupValue = TXshSimpleLevel::Scanned;
    yetToCleanupCell            = (frameStatus & mask) == yetToCleanupValue;
  }

  // paint cell
  p.fillRect(rect, QBrush(cellColor));
  // paint side bar
  p.fillRect(QRect(x, y, 7, RowHeight), QBrush(sideColor));

  if (yetToCleanupCell)
    p.fillRect(
        rect.adjusted(rect.width() / 2, 0, 0, 0),
        (isSelected) ? SelectedFullcolorColumnColor : FullcolorColumnColor);

  // draw dot line if the column is locked
  TXshColumn *column = xsh->getColumn(col);
  if (column->isLocked()) {
    p.setPen(QPen(cellColor, 2, Qt::DotLine));
    p.drawLine(x + 3, y, x + 3, y + RowHeight);
  }
  // draw "end of the level"
  if (nextCell.isEmpty() ||
      cell.m_level.getPointer() != nextCell.m_level.getPointer()) {
    QPainterPath path(QPointF(x, y + RowHeight));
    path.lineTo(QPointF(x + 7, y + RowHeight));
    path.lineTo(QPointF(x + 7, y + RowHeight - 7));
    path.lineTo(QPointF(x, y + RowHeight));
    p.fillPath(path, QBrush(cellColor));
  }

  bool sameLevel = prevCell.m_level.getPointer() == cell.m_level.getPointer();

  int distance, offset;
  TApp::instance()->getCurrentScene()->getScene()->getProperties()->getMarkers(
      distance, offset);
  if (distance == 0) distance = 6;
  bool isAfterMarkers         = (row - offset) % distance == 0;

  // draw marker interval
  if (isAfterMarkers) {
    p.setPen(m_viewer->getMarkerLineColor());
    p.drawLine(x, y, x + 6, y);
  }

  QRect nameRect = rect.adjusted(7, 4, -6, 0);

  if (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled()) {
    TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col));
    int r0, r1;
    if (pegbar && pegbar->getKeyframeRange(r0, r1))
      nameRect.adjust(0, 0, -9, 0);
  }

  // draw text in red if the file does not exist
  bool isRed                                  = false;
  TXshSimpleLevel *sl                         = cell.getSimpleLevel();
  if (sl && !sl->isFid(cell.m_frameId)) isRed = true;
  TXshChildLevel *cl                          = cell.getChildLevel();
  if (cl && cell.getFrameId().getNumber() - 1 >= cl->getFrameCount())
    isRed = true;
  p.setPen(isRed ? m_viewer->getSelectedColumnTextColor()
                 : m_viewer->getTextColor());

#ifdef _WIN32
  static QFont font("Arial", XSHEET_FONT_SIZE, QFont::Normal);
#else
  static QFont font("Helvetica", XSHEET_FONT_SIZE, QFont::Normal);
#endif
  p.setFont(font);

  // if the same level & same fId with the previous cell, then draw vertical
  // line
  if (sameLevel && prevCell.m_frameId == cell.m_frameId) {  // cella uguale a
                                                            // quella precedente
                                                            // (non sulla marker
                                                            // line):
    int x = (Preferences::instance()->isLevelNameOnEachMarkerEnabled())
                ? rect.right() - 11
                : rect.center().x();
    p.drawLine(x, rect.top(), x, rect.bottom());
  }
  // draw frame number
  else {
    TFrameId fid = cell.m_frameId;

    // convert the last one digit of the frame number to alphabet
    // Ex.  12 -> 1B    21 -> 2A   30 -> 3
    if (Preferences::instance()->isShowFrameNumberWithLettersEnabled())
      p.drawText(nameRect, Qt::AlignRight,
                 m_viewer->getFrameNumberWithLetters(fid.getNumber()));
    else {
      std::string frameNumber("");
      // set number
      if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber());
      // add letter
      if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter());
      p.drawText(nameRect, Qt::AlignRight, QString::fromStdString(frameNumber));
    }
  }

  // draw level name
  if (!sameLevel ||
      (isAfterMarkers &&
       Preferences::instance()->isLevelNameOnEachMarkerEnabled())) {
    std::wstring levelName = cell.m_level->getName();
    QString text           = QString::fromStdWString(levelName);
#if QT_VERSION >= 0x050500
    QFontMetrics fm(font);
    QString elidaName = elideText(text, fm, nameRect.width(), QString("~"));
#else
    QString elidaName = elideText(text, font, nameRect.width());
#endif
    p.drawText(nameRect, Qt::AlignLeft, elidaName);
  }
}

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

void CellArea::drawSoundTextCell(QPainter &p, int row, int col) {
  TXsheet *xsh  = m_viewer->getXsheet();
  TXshCell cell = xsh->getCell(row, col);
  TXshCell prevCell;
  if (row > 0) prevCell = xsh->getCell(row - 1, col);
  TXshCell nextCell;
  nextCell = xsh->getCell(row + 1, col);

  if (cell.isEmpty() && prevCell.isEmpty()) return;
  int x      = m_viewer->columnToX(col);
  int y      = m_viewer->rowToY(row);
  QRect rect = QRect(x + 1, y + 1, ColumnWidth - 1, RowHeight - 1);
  if (cell.isEmpty()) {  // vuol dire che la precedente non e' vuota
    p.setPen(m_viewer->getLightLineColor());
    p.drawLine(rect.topLeft(), rect.bottomRight());
    p.drawLine(rect.topRight(), rect.bottomLeft());
    return;
  }

  int levelType;
  QColor cellColor, sideColor;
  m_viewer->getCellTypeAndColors(levelType, cellColor, sideColor, cell);
  p.fillRect(rect, QBrush(cellColor));

  if (nextCell.isEmpty() ||
      cell.m_level.getPointer() != nextCell.m_level.getPointer()) {
    QPainterPath path(QPointF(x, y));
    path.lineTo(QPointF(x + 6, y));
    path.lineTo(QPointF(x + 6, y + 2));
    path.lineTo(QPointF(x, y + RowHeight - 2));
    p.fillPath(path, QBrush(sideColor));
  } else
    p.fillRect(QRect(x, y, 6, RowHeight), QBrush(sideColor));

  bool sameLevel = prevCell.m_level.getPointer() == cell.m_level.getPointer();

  TFrameId fid = cell.m_frameId;
  if (sameLevel &&
      prevCell.m_frameId == fid) {  // cella uguale a quella precedente
    // non scrivo nulla e disegno una linea verticale
    QPen oldPen = p.pen();
    p.setPen(QPen(Qt::black, 1, Qt::DotLine));
    int x = rect.center().x();
    p.drawLine(x, rect.top(), x, rect.bottom());
    p.setPen(oldPen);
    return;
  }
  QString text = cell.getSoundTextLevel()->getFrameText(fid.getNumber() - 1);
  p.setPen(Qt::black);
  QRect nameRect = rect.adjusted(8, -H_ADJUST, -10, H_ADJUST);
  // il nome va scritto se e' diverso dalla cella precedente oppure se
  // siamo su una marker line
  static QFont font("Helvetica", XSHEET_FONT_SIZE, QFont::Normal);
  p.setFont(font);

#if QT_VERSION >= 0x050500
  QFontMetrics metric(font);
  QString elidaName = elideText(text, metric, nameRect.width(), "~");
#else
  QString elidaName   = elideText(text, font, nameRect.width(), "~");
#endif

  if (!sameLevel || prevCell.m_frameId != fid)
    p.drawText(nameRect, Qt::AlignLeft, elidaName);
}

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

void CellArea::drawPaletteCell(QPainter &p, int row, int col,
                               bool isReference) {
  TXsheet *xsh  = m_viewer->getXsheet();
  TXshCell cell = xsh->getCell(row, col);
  TXshCell prevCell;

  TCellSelection *cellSelection = m_viewer->getCellSelection();
  bool isSelected               = cellSelection->isCellSelected(row, col);

  if (row > 0) prevCell = xsh->getCell(row - 1, col);
  TXshCell nextCell     = xsh->getCell(row + 1, col);

  if (cell.isEmpty() && prevCell.isEmpty()) return;
  int x      = m_viewer->columnToX(col);
  int y      = m_viewer->rowToY(row);
  QRect rect = QRect(x + 1, y + 1, ColumnWidth - 1, RowHeight - 1);
  if (cell.isEmpty()) {  // vuol dire che la precedente non e' vuota
    QColor levelEndColor = m_viewer->getTextColor();
    levelEndColor.setAlphaF(0.3);
    p.setPen(levelEndColor);
    p.drawLine(rect.topLeft(), rect.bottomRight());
    p.drawLine(rect.topRight(), rect.bottomLeft());
    return;
  }

  QColor cellColor, sideColor;
  if (isReference) {
    cellColor = (isSelected) ? m_viewer->getSelectedReferenceColumnColor()
                             : m_viewer->getReferenceColumnColor();
    sideColor = m_viewer->getReferenceColumnBorderColor();
  } else {
    cellColor = (isSelected) ? m_viewer->getSelectedPaletteColumnColor()
                             : m_viewer->getPaletteColumnColor();
    sideColor = m_viewer->getPaletteColumnBorderColor();
  }

  p.fillRect(rect, QBrush(cellColor));
  p.fillRect(QRect(x, y, 7, RowHeight), QBrush(sideColor));

  TXshColumn *column = xsh->getColumn(col);
  if (column->isLocked()) {
    p.setPen(QPen(cellColor, 2, Qt::DotLine));
    p.drawLine(x + 3, y, x + 3, y + RowHeight);
  }

  if (nextCell.isEmpty() ||
      cell.m_level.getPointer() != nextCell.m_level.getPointer()) {
    QPainterPath path(QPointF(x, y + RowHeight));
    path.lineTo(QPointF(x + 7, y + RowHeight));
    path.lineTo(QPointF(x + 7, y + RowHeight - 7));
    path.lineTo(QPointF(x, y + RowHeight));
    p.fillPath(path, QBrush(cellColor));
  }

  bool sameLevel = prevCell.m_level.getPointer() == cell.m_level.getPointer();

  int distance, offset;
  TApp::instance()->getCurrentScene()->getScene()->getProperties()->getMarkers(
      distance, offset);
  if (distance == 0) distance = 6;
  bool isAfterMarkers         = (row - offset) % distance == 0;

  if (isAfterMarkers) {
    p.setPen(m_viewer->getMarkerLineColor());
    p.drawLine(x, y, x + 6, y);
  }

  if (sameLevel && prevCell.m_frameId == cell.m_frameId &&
      !isAfterMarkers) {  // cella uguale a quella precedente (non sulla marker
                          // line):
                          // non scrivo nulla e disegno una linea verticale
    QPen oldPen = p.pen();
    p.setPen(QPen(m_viewer->getTextColor(), 1));
    int x = rect.center().x();
    p.drawLine(x, rect.top(), x, rect.bottom());
    p.setPen(oldPen);
  } else {
    TFrameId fid = cell.m_frameId;

    std::wstring levelName = cell.m_level->getName();
    std::string frameNumber("");
    if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber());
    if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter());

    QRect nameRect = rect.adjusted(7, 4, -12, 0);

    bool isRed                         = false;
    TXshPaletteLevel *pl               = cell.getPaletteLevel();
    if (pl && !pl->getPalette()) isRed = true;
    p.setPen(isRed ? m_viewer->getSelectedColumnTextColor()
                   : m_viewer->getTextColor());
// il nome va scritto se e' diverso dalla cella precedente oppure se
// siamo su una marker line
#ifndef _WIN32
    static QFont font("Arial", XSHEET_FONT_SIZE, QFont::Normal);
#else
    static QFont font("Helvetica", XSHEET_FONT_SIZE, QFont::Normal);
#endif
    p.setFont(font);
    QFontMetrics fm(font);

    // il numero va scritto se e' diverso dal precedente oppure se il livello
    // e' diverso dal precedente
    QString numberStr;
    if (!sameLevel || prevCell.m_frameId != cell.m_frameId) {
      numberStr = QString::fromStdString(frameNumber);
      p.drawText(nameRect, Qt::AlignRight, numberStr);
    }

    QString text = QString::fromStdWString(levelName);
#if QT_VERSION >= 0x050500
    QString elidaName = elideText(
        text, fm, nameRect.width() - fm.width(numberStr) - 2, QString("~"));
#else
    QString elidaName = elideText(
        text, font, nameRect.width() - fm.width(numberStr) - 2, QString("~"));
#endif

    if (!sameLevel || isAfterMarkers)
      p.drawText(nameRect, Qt::AlignLeft, elidaName);
  }
}

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

void CellArea::drawKeyframe(QPainter &p, const QRect toBeUpdated) {
  int r0, r1, c0, c1;  // range of visible rows and columns
  r0 = m_viewer->yToRow(toBeUpdated.top());
  r1 = m_viewer->yToRow(toBeUpdated.bottom());
  c0 = m_viewer->xToColumn(toBeUpdated.left());
  c1 = m_viewer->xToColumn(toBeUpdated.right());

  static QPixmap selectedKey = QPixmap(":Resources/selected_key.bmp");
  static QPixmap key         = QPixmap(":Resources/key.bmp");
  int keyPixOffset           = (RowHeight - key.height()) / 2;

  TXsheet *xsh         = m_viewer->getXsheet();
  ColumnFan *columnFan = xsh->getColumnFan();
  int col;
  for (col = c0; col <= c1; col++) {
    if (!columnFan->isActive(col)) continue;
    int x                = m_viewer->columnToX(col);
    TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col));
    if (!pegbar) return;
    int row0, row1;
    bool emptyKeyframe = !pegbar->getKeyframeRange(row0, row1);
    if (emptyKeyframe) continue;
    bool emptyKeyframeRange = row0 >= row1;
    int row;
    row0 = std::max(row0, r0);
    row1 = std::min(row1, r1);

    /*- first, draw key segments -*/
    p.setPen(m_viewer->getTextColor());
    int x_line = x + ColumnWidth - 8;
    for (row = row0; row <= row1; row++) {
      int hr0, hr1;
      double e0, e1;
      if (pegbar->getKeyframeSpan(row, hr0, e0, hr1, e1)) {
        int y0 = m_viewer->rowToY(hr0 + 1) - keyPixOffset;
        int y1 = m_viewer->rowToY(hr1) + keyPixOffset;
        p.drawLine(x_line, y0, x_line, y1);
        if (hr1 - hr0 > 4) {
          int rh0, rh1;
          if (getEaseHandles(hr0, hr1, e0, e1, rh0, rh1)) {
            int e0Y = m_viewer->rowToY(rh0) + keyPixOffset;
            drawArrow(p, QPointF(x_line - 4, e0Y + 2),
                      QPointF(x_line + 4, e0Y + 2), QPointF(x_line, e0Y + 6),
                      true, m_viewer->getLightLineColor(),
                      m_viewer->getTextColor());
            int e1Y = m_viewer->rowToY(rh1 + 1) - keyPixOffset;
            drawArrow(p, QPointF(x_line - 4, e1Y - 2),
                      QPointF(x_line + 4, e1Y - 2), QPointF(x_line, e1Y - 6),
                      true, m_viewer->getLightLineColor(),
                      m_viewer->getTextColor());
          }
        }
        // jump to next segment
        row = hr1 - 1;
      } else if (pegbar->isKeyframe(row) && pegbar->isKeyframe(row + 1)) {
        int y0 = m_viewer->rowToY(row + 1);
        p.drawLine(x_line, y0 - keyPixOffset, x_line, y0 + keyPixOffset);
      }
    }

    /*- then draw keyframe pixmaps -*/
    int x1 = x + ColumnWidth - 13;
    for (row = row0; row <= row1; row++) {
      int y = m_viewer->rowToY(row) + 1;
      p.setPen(m_viewer->getTextColor());
      if (pegbar->isKeyframe(row)) {
        if (m_viewer->getKeyframeSelection() &&
            m_viewer->getKeyframeSelection()->isSelected(row, col)) {
          // keyframe selezionato
          p.drawPixmap(x1, y + keyPixOffset, selectedKey);
        } else {
          // keyframe non selezionato
          p.drawPixmap(x1, y + keyPixOffset, key);
        }
      }
    }

    int y1 = m_viewer->rowToY(row1 + 1);
    if (!emptyKeyframeRange && row0 <= row1 + 1) {
      // c'e' piu' di un keyframe
      // disegno il bottone per il ciclo
      p.setBrush(Qt::white);
      p.setPen(Qt::black);
      p.drawRect(x1, y1, 10, 10);
      p.setBrush(Qt::NoBrush);
      // disegno il bordo in basso (arrotondato)
      p.drawLine(x1 + 1, y1 + 10, x1 + 9, y1 + 10);
      p.setPen(Qt::white);
      p.drawLine(x1 + 3, y1 + 10, x1 + 7, y1 + 10);
      p.setPen(Qt::black);
      p.drawLine(x1 + 3, y1 + 11, x1 + 7, y1 + 11);

      // disegno la freccia
      p.drawArc(QRect(x1 + 2, y1 + 3, 6, 6), 180 * 16, 270 * 16);
      p.drawLine(x1 + 5, y1 + 2, x1 + 5, y1 + 5);
      p.drawLine(x1 + 5, y1 + 2, x1 + 8, y1 + 2);
    }
    if (pegbar->isCycleEnabled()) {
      // la riga a zigzag sotto il bottone
      int ymax = m_viewer->rowToY(r1 + 1);
      int qy   = y1 + 12;
      int zig  = 2;
      int qx   = x1 + 5;
      p.setPen(m_viewer->getTextColor());
      p.drawLine(qx, qy, qx - zig, qy + zig);
      while (qy < ymax) {
        p.drawLine(qx - zig, qy + zig, qx + zig, qy + 3 * zig);
        p.drawLine(qx + zig, qy + 3 * zig, qx - zig, qy + 5 * zig);
        qy += 4 * zig;
      }
    }
  }
}
//-----------------------------------------------------------------------------

void CellArea::drawNotes(QPainter &p, const QRect toBeUpdated) {
  int r0, r1, c0, c1;  // range of visible rows and columns
  r0 = m_viewer->yToRow(toBeUpdated.top());
  r1 = m_viewer->yToRow(toBeUpdated.bottom());
  c0 = m_viewer->xToColumn(toBeUpdated.left());
  c1 = m_viewer->xToColumn(toBeUpdated.right());

  TXsheet *xsh = m_viewer->getXsheet();
  if (!xsh) return;
  TXshNoteSet *notes = xsh->getNotes();
  int notesCount     = notes->getCount();
  int i;
  for (i = 0; i < notesCount; i++) {
    QList<NoteWidget *> noteWidgets = m_viewer->getNotesWidget();
    int widgetCount                 = noteWidgets.size();
    NoteWidget *noteWidget          = 0;
    if (i < widgetCount)
      noteWidget = noteWidgets.at(i);
    else {
      noteWidget = new NoteWidget(m_viewer, i);
      m_viewer->addNoteWidget(noteWidget);
    }
    if (!noteWidget) continue;
    int r = notes->getNoteRow(i);
    int c = notes->getNoteCol(i);
    if (r < r0 || r > r1 || c < c0 || c > c1) continue;
    TPointD pos = notes->getNotePos(i) +
                  TPointD(m_viewer->columnToX(c), m_viewer->rowToY(r));
    noteWidget->paint(&p, QPoint(pos.x, pos.y),
                      i == m_viewer->getCurrentNoteIndex());
  }
}

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

bool CellArea::getEaseHandles(int r0, int r1, double e0, double e1, int &rh0,
                              int &rh1) {
  if (r1 <= r0 + 4) {
    rh0 = r0;
    rh1 = r1;
    return false;
  }
  if (e0 < 0 || e1 < 0) {
    rh0 = r0;
    rh1 = r1;
    return false;
  }
  if (e0 <= 0 && e1 <= 0) {
    rh0 = r0 + 1;
    rh1 = r1 - 1;
  } else if (e0 <= 0) {
    rh0   = r0 + 1;
    int a = rh0 + 1;
    int b = r1 - 2;
    assert(a <= b);
    rh1 = tcrop((int)(r1 - e1 + 0.5), a, b);
  } else if (e1 <= 0) {
    rh1   = r1 - 1;
    int b = rh1 - 1;
    int a = r0 + 2;
    assert(a <= b);
    rh0 = tcrop((int)(r0 + e0 + 0.5), a, b);
  } else {
    int m = tfloor(0.5 * (r0 + e0 + r1 - e1));
    m     = tcrop(m, r0 + 2, r1 - 2);
    int a = r0 + 2;
    int b = std::min(m, r1 - 3);
    assert(a <= b);
    rh0 = tcrop((int)(r0 + e0 + 0.5), a, b);
    a   = rh0 + 1;
    b   = r1 - 2;
    assert(a <= b);
    rh1 = tcrop((int)(r1 - e1 + 0.5), a, b);
  }
  return true;
}

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

void CellArea::paintEvent(QPaintEvent *event) {
  QRect toBeUpdated = event->rect();

  QPainter p(this);
  p.setClipRect(toBeUpdated);
  p.fillRect(toBeUpdated, QBrush(m_viewer->getEmptyCellColor()));

  drawCells(p, toBeUpdated);
  if (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled())
    drawKeyframe(p, toBeUpdated);
  drawNotes(p, toBeUpdated);

  if (getDragTool()) getDragTool()->drawCellsArea(p);

  int row    = m_viewer->getCurrentRow();
  int col    = m_viewer->getCurrentColumn();
  int x      = m_viewer->columnToX(col);
  int y      = m_viewer->rowToY(row);
  QRect rect = QRect(x + 1, y + 1, ColumnWidth - 3, RowHeight - 2);
  p.setPen(Qt::black);
  p.setBrush(Qt::NoBrush);
  p.drawRect(rect);
}

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

class CycleUndo : public TUndo {
  TStageObject *m_pegbar;
  CellArea *m_area;

public:
  // indices sono le colonne inserite
  CycleUndo(TStageObject *pegbar, CellArea *area)
      : m_pegbar(pegbar), m_area(area) {}
  void undo() const {
    m_pegbar->enableCycle(!m_pegbar->isCycleEnabled());
    m_area->update();
  }
  void redo() const { undo(); }
  int getSize() const { return sizeof *this; }

  QString getHistoryString() {
    return QObject::tr("Toggle cycle of  %1")
        .arg(QString::fromStdString(m_pegbar->getName()));
  }
  int getHistoryType() { return HistoryType::Xsheet; }
};
//----------------------------------------------------------

void CellArea::mousePressEvent(QMouseEvent *event) {
  m_viewer->setQtModifiers(event->modifiers());
  assert(!m_isPanning);
  m_isMousePressed = true;
  if (event->button() == Qt::LeftButton) {
    assert(getDragTool() == 0);

    TPoint pos(event->pos().x(), event->pos().y());
    int row = m_viewer->yToRow(pos.y);
    int col = m_viewer->xToColumn(pos.x);
    int x0  = m_viewer->columnToX(col);
    int y1  = m_viewer->rowToY(row) - 1;
    int x   = pos.x - x0;

    // Verifico se ho cliccato su una nota
    TXshNoteSet *notes = m_viewer->getXsheet()->getNotes();
    int i;
    for (i = notes->getCount() - 1; i >= 0; i--) {
      int r       = notes->getNoteRow(i);
      int c       = notes->getNoteCol(i);
      TPointD pos = notes->getNotePos(i) +
                    TPointD(m_viewer->columnToX(c), m_viewer->rowToY(r));
      QRect rect(pos.x, pos.y, NoteWidth, NoteHeight);
      if (!rect.contains(event->pos())) continue;
      setDragTool(XsheetGUI::DragTool::makeNoteMoveTool(m_viewer));
      m_viewer->setCurrentNoteIndex(i);
      m_viewer->dragToolClick(event);
      event->accept();
      update();
      return;
    }
    // Se non ho cliccato su una nota e c'e' una nota selezionata la
    // deseleziono.
    if (m_viewer->getCurrentNoteIndex() >= 0) m_viewer->setCurrentNoteIndex(-1);

    TXsheet *xsh       = m_viewer->getXsheet();
    TXshColumn *column = xsh->getColumn(col);

    // Verifico se e' una colonna sound
    bool isSoundColumn = false;
    if (column) {
      TXshSoundColumn *soundColumn = column->getSoundColumn();
      isSoundColumn                = (!soundColumn) ? false : true;
    }

    // TObjectHandle *oh = TApp::instance()->getCurrentObject();
    // oh->setObjectId(m_viewer->getObjectId(col));

    // gmt, 28/12/2009. Non dovrebbe essere necessario, visto che dopo
    // verra cambiata la colonna e quindi l'oggetto corrente
    // Inoltre, facendolo qui, c'e' un problema con il calcolo del dpi
    // (cfr. SceneViewer::onLevelChanged()): setObjectId() chiama (in cascata)
    // onLevelChanged(), ma la colonna corrente risulta ancora quella di prima e
    // quindi
    // il dpi viene calcolato male. Quando si cambia la colonna l'oggetto
    // corrente risulta
    // gia' aggiornato e quindi non ci sono altre chiamate a onLevelChanged()
    // cfr bug#5235

    TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col));

    if (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled()) {
      int k0, k1;
      bool isKeyFrameArea =
          (pegbar && pegbar->getKeyframeRange(k0, k1) &&
           (k1 > k0 || k0 == row) && k0 <= row && row <= k1 + 1 &&
           ColumnWidth - 13 <= x && x <= ColumnWidth)
              ? true
              : false;

      if (isKeyFrameArea) {           // They are in the keyframe selection
        if (pegbar->isKeyframe(row))  // in the keyframe
        {
          m_viewer->setCurrentRow(
              row);  // If you click on the key, change the current row as well
          setDragTool(XsheetGUI::DragTool::makeKeyframeMoverTool(m_viewer));
        } else {
          int r0, r1;
          double e0, e1;
          int rh0, rh1;
          if (pegbar->getKeyframeSpan(row, r0, e0, r1, e1) &&
              getEaseHandles(r0, r1, e0, e1, rh0, rh1)) {
            if (rh0 == row)  // in a keyframe handle
              setDragTool(XsheetGUI::DragTool::makeKeyFrameHandleMoverTool(
                  m_viewer, true, r0));
            else if (rh1 == row)  // in a keyframe handle
              setDragTool(XsheetGUI::DragTool::makeKeyFrameHandleMoverTool(
                  m_viewer, false, r1));
          }
          if (row == k1 + 1)  // in the cycle toggle
          {
            pegbar->enableCycle(!pegbar->isCycleEnabled());
            TUndoManager::manager()->add(new CycleUndo(pegbar, this));
          }
        }
        m_viewer->dragToolClick(event);
        event->accept();
        update();
        return;
      }
    }

    if ((!xsh->getCell(row, col).isEmpty()) && x < 6) {
      TXshColumn *column = xsh->getColumn(col);
      if (column && !m_viewer->getCellSelection()->isCellSelected(row, col)) {
        int r0, r1;
        column->getLevelRange(row, r0, r1);
        if (event->modifiers() & Qt::ControlModifier) {
          m_viewer->getCellKeyframeSelection()->makeCurrent();
          m_viewer->getCellKeyframeSelection()->selectCellsKeyframes(r0, col,
                                                                     r1, col);
        } else {
          m_viewer->getKeyframeSelection()->selectNone();
          m_viewer->getCellSelection()->makeCurrent();
          m_viewer->getCellSelection()->selectCells(r0, col, r1, col);
        }
        TApp::instance()->getCurrentSelection()->notifySelectionChanged();
      }
      TSelection *selection =
          TApp::instance()->getCurrentSelection()->getSelection();
      if (TCellKeyframeSelection *cellKeyframeSelection =
              dynamic_cast<TCellKeyframeSelection *>(selection))
        setDragTool(XsheetGUI::DragTool::makeCellKeyframeMoverTool(m_viewer));
      else
        setDragTool(XsheetGUI::DragTool::makeLevelMoverTool(m_viewer));
    } else {
      m_viewer->getKeyframeSelection()->selectNone();
      if (isSoundColumn && x > ColumnWidth - 6 && x < ColumnWidth)
        setDragTool(XsheetGUI::DragTool::makeSoundScrubTool(
            m_viewer, column->getSoundColumn()));
      else if (m_levelExtenderRect.contains(pos.x, pos.y))
        setDragTool(XsheetGUI::DragTool::makeLevelExtenderTool(m_viewer));
      else if (event->modifiers() & Qt::ControlModifier &&
               m_upperLevelExtenderRect.contains(pos.x, pos.y))
        setDragTool(XsheetGUI::DragTool::makeLevelExtenderTool(m_viewer, true));
      else if (isSoundColumn &&
               rectContainsPos(m_soundLevelModifyRects, event->pos()))
        setDragTool(XsheetGUI::DragTool::makeSoundLevelModifierTool(m_viewer));
      else
        setDragTool(XsheetGUI::DragTool::makeSelectionTool(m_viewer));
    }
    m_viewer->dragToolClick(event);
  } else if (event->button() == Qt::MidButton) {
    m_pos       = event->pos();
    m_isPanning = true;
  }
  event->accept();
  update();
}

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

void CellArea::mouseMoveEvent(QMouseEvent *event) {
  m_viewer->setQtModifiers(event->modifiers());
  setCursor(Qt::ArrowCursor);
  QPoint pos        = event->pos();
  QRect visibleRect = visibleRegion().boundingRect();
  if (m_isPanning) {
    // Pan tasto centrale
    m_viewer->scroll(m_pos - pos);
    return;
  }
  if ((event->buttons() & Qt::LeftButton) != 0 &&
      !visibleRegion().contains(pos)) {
    QRect bounds = visibleRegion().boundingRect();
    m_viewer->setAutoPanSpeed(bounds, pos);
  } else
    m_viewer->stopAutoPan();

  m_pos = pos;
  if (getDragTool()) {
    getDragTool()->onDrag(event);
    return;
  }

  int row = m_viewer->yToRow(pos.y());
  int col = m_viewer->xToColumn(pos.x());
  int x0  = m_viewer->columnToX(col);
  int x   = m_pos.x() - x0;

  TXsheet *xsh = m_viewer->getXsheet();

  // Verifico se e' una colonna sound
  TXshColumn *column  = xsh->getColumn(col);
  bool isSoundColumn  = false;
  bool isZeraryColumn = false;
  if (column) {
    TXshSoundColumn *soundColumn     = column->getSoundColumn();
    isSoundColumn                    = (!soundColumn) ? false : true;
    TXshZeraryFxColumn *zeraryColumn = column->getZeraryFxColumn();
    isZeraryColumn                   = (!zeraryColumn) ? false : true;
  }

  TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col));
  int k0, k1;
  if (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled() &&
      pegbar && pegbar->getKeyframeRange(k0, k1) && k0 <= row &&
      row <= k1 + 1 && ColumnWidth - 13 <= x && x <= ColumnWidth) {
    if (pegbar->isKeyframe(row))  // key frame
      m_tooltip = tr("Click to select keyframe, drag to move it");
    else {
      int r0, r1;
      double e0, e1;
      int rh0, rh1;
      if (pegbar->getKeyframeSpan(row, r0, e0, r1, e1) &&
          getEaseHandles(
              r0, r1, e0, e1, rh0,
              rh1)) {  // triangles in the segment betweeen key frames
        if (rh0 == row)
          m_tooltip = tr("Click and drag to set the acceleration range");
        else if (rh1 == row)
          m_tooltip = tr("Click and drag to set the deceleration range");
      }
      if (row == k1 + 1)  // cycle toggle of key frames
        m_tooltip = tr("Set the cycle of previous keyframes");
    }
  } else if ((!xsh->getCell(row, col).isEmpty() || isSoundColumn) && x < 6)
    m_tooltip = tr("Click and drag to move the selection");
  else if (isZeraryColumn)
    m_tooltip = QString::fromStdWString(column->getZeraryFxColumn()
                                            ->getZeraryColumnFx()
                                            ->getZeraryFx()
                                            ->getName());
  else if ((!xsh->getCell(row, col).isEmpty() && !isSoundColumn) && x > 6 &&
           x < ColumnWidth) {
    TXshCell cell          = xsh->getCell(row, col);
    TFrameId fid           = cell.getFrameId();
    std::wstring levelName = cell.m_level->getName();

    // convert the last one digit of the frame number to alphabet
    // Ex.  12 -> 1B    21 -> 2A   30 -> 3
    if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) {
      m_tooltip =
          (fid.isEmptyFrame() || fid.isNoFrame())
              ? QString::fromStdWString(levelName)
              : QString::fromStdWString(levelName) + QString(" ") +
                    m_viewer->getFrameNumberWithLetters(fid.getNumber());
    } else {
      std::string frameNumber("");
      if (fid.getNumber() > 0) frameNumber = std::to_string(fid.getNumber());
      if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter());
      m_tooltip =
          QString((frameNumber.empty())
                      ? QString::fromStdWString(levelName)
                      : QString::fromStdWString(levelName) + QString(" ") +
                            QString::fromStdString(frameNumber));
    }
  } else if (isSoundColumn && x > ColumnWidth - 6 && x < ColumnWidth)
    m_tooltip = tr("Click and drag to play");
  else if (m_levelExtenderRect.contains(pos))
    m_tooltip = tr("Click and drag to repeat selected cells");
  else if (isSoundColumn && rectContainsPos(m_soundLevelModifyRects, pos)) {
    setCursor(Qt::SplitVCursor);
    m_tooltip = tr("");
  } else
    m_tooltip = tr("");
}

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

void CellArea::mouseReleaseEvent(QMouseEvent *event) {
  m_viewer->setQtModifiers(0);
  m_isMousePressed = false;
  m_viewer->stopAutoPan();
  m_isPanning = false;
  m_viewer->dragToolRelease(event);
}

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

void CellArea::mouseDoubleClickEvent(QMouseEvent *event) {
  TPoint pos(event->pos().x(), event->pos().y());
  int row = m_viewer->yToRow(pos.y);
  int col = m_viewer->xToColumn(pos.x);
  // Se la colonna e' sound non devo fare nulla
  TXshColumn *column = m_viewer->getXsheet()->getColumn(col);
  if (column && (column->getSoundColumn() || column->getSoundTextColumn()))
    return;

  // Se ho cliccato su una nota devo aprire il popup
  TXshNoteSet *notes = m_viewer->getXsheet()->getNotes();
  int i;
  for (i = notes->getCount() - 1; i >= 0; i--) {
    int r       = notes->getNoteRow(i);
    int c       = notes->getNoteCol(i);
    TPointD pos = notes->getNotePos(i) +
                  TPointD(m_viewer->columnToX(c), m_viewer->rowToY(r));
    QRect rect(pos.x, pos.y, NoteWidth, NoteHeight);
    if (!rect.contains(event->pos())) continue;
    m_viewer->setCurrentNoteIndex(i);
    m_viewer->getNotesWidget().at(i)->openNotePopup();
    return;
  }

  TObjectHandle *oh = TApp::instance()->getCurrentObject();
  oh->setObjectId(m_viewer->getObjectId(col));

  if (Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled()) {
    int x = pos.x - m_viewer->columnToX(col);
    TStageObject *pegbar =
        m_viewer->getXsheet()->getStageObject(m_viewer->getObjectId(col));
    bool isKeyFrameArea = (pegbar && pegbar->isKeyframe(row) &&
                           ColumnWidth - 13 <= x && x <= ColumnWidth)
                              ? true
                              : false;
    // If you are in the keyframe area, open a function editor
    if (isKeyFrameArea) {
      QAction *action =
          CommandManager::instance()->getAction(MI_OpenFunctionEditor);
      action->trigger();
      return;
    }
  }

  if (col == -1) return;

  // in modalita' xsheet as animation sheet non deve essere possibile creare
  // livelli con doppio click: se la cella e' vuota non bisogna fare nulla
  if ((Preferences::instance()->isAnimationSheetEnabled() &&
       m_viewer->getXsheet()->getCell(row, col).isEmpty()))
    return;

  m_renameCell->showInRowCol(row, col);
}

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

void CellArea::contextMenuEvent(QContextMenuEvent *event) {
  TPoint pos(event->pos().x(), event->pos().y());
  int row = m_viewer->yToRow(pos.y);
  int col = m_viewer->xToColumn(pos.x);

  QMenu menu(this);

  // Verifico se ho cliccato su una nota
  TXshNoteSet *notes = m_viewer->getXsheet()->getNotes();
  int i;
  for (i = notes->getCount() - 1; i >= 0; i--) {
    int r       = notes->getNoteRow(i);
    int c       = notes->getNoteCol(i);
    TPointD pos = notes->getNotePos(i) +
                  TPointD(m_viewer->columnToX(c), m_viewer->rowToY(r));
    QRect rect(pos.x, pos.y, NoteWidth, NoteHeight);
    if (!rect.contains(event->pos())) continue;
    m_viewer->setCurrentNoteIndex(i);
    createNoteMenu(menu);
    if (!menu.isEmpty()) menu.exec(event->globalPos());
    return;
  }

  TXsheet *xsh         = m_viewer->getXsheet();
  int x0               = m_viewer->columnToX(col);
  int y1               = m_viewer->rowToY(row) - 1;
  int x                = pos.x - x0;
  TStageObject *pegbar = xsh->getStageObject(m_viewer->getObjectId(col));
  int k0, k1;
  int r0, r1, c0, c1;
  if (col >= 0) m_viewer->getCellSelection()->getSelectedCells(r0, c0, r1, c1);

  if (pegbar && pegbar->getKeyframeRange(k0, k1) && k0 <= row && row <= k1 &&
      ColumnWidth - 13 <= x && x <= ColumnWidth &&
      Preferences::instance()->isShowKeyframesOnXsheetCellAreaEnabled()) {
    TStageObjectId objectId;
    if (col < 0)
      objectId = TStageObjectId::CameraId(0);
    else {  // Set the current column and the current object
      objectId = TStageObjectId::ColumnId(col);
      m_viewer->setCurrentColumn(col);
    }
    TApp::instance()->getCurrentObject()->setObjectId(objectId);
    m_viewer->setCurrentRow(row);
    if (pegbar->isKeyframe(row)) {  // clicking on keyframes
      TKeyframeSelection *keyframeSelection = m_viewer->getKeyframeSelection();
      keyframeSelection->select(row, col);
      keyframeSelection->makeCurrent();
      createKeyMenu(menu);
    } else if (!xsh->getColumn(col) ||
               !xsh->getColumn(col)
                    ->isLocked())  // on the line between two keyframes
      createKeyLineMenu(menu, row, col);
  } else if (col >= 0 &&  // Non e' la colonna di camera
             m_viewer->getCellSelection()->isCellSelected(
                 row, col) &&  // La cella e' selezionata
             (abs(r1 - r0) > 0 ||
              abs(c1 - c0) >
                  0))  // Il numero di celle selezionate e' maggiore di 1
  {                    // Sono su una selezione di celle
    m_viewer->setCurrentColumn(col);
    int e, f;
    bool areCellsEmpty = false;
    for (e = r0; e <= r1; e++) {
      for (f = c0; f <= c1; f++)
        if (!xsh->getCell(e, f).isEmpty()) {
          areCellsEmpty = true;
          break;
        }
      if (areCellsEmpty) break;
    }
    createCellMenu(menu, areCellsEmpty);
  } else {
    if (col >= 0) {
      m_viewer->getCellSelection()->makeCurrent();
      m_viewer->getCellSelection()->selectCell(row, col);
      m_viewer->setCurrentColumn(col);
    }
    if (!xsh->getCell(row, col).isEmpty())
      createCellMenu(menu, true);
    else
      createCellMenu(menu, false);
  }

  if (!menu.isEmpty()) menu.exec(event->globalPos());
}

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

void CellArea::dragEnterEvent(QDragEnterEvent *e) {
  if (acceptResourceOrFolderDrop(e->mimeData()->urls()) ||
      e->mimeData()->hasFormat(CastItems::getMimeFormat()) ||
      e->mimeData()->hasFormat("application/vnd.toonz.drawings")) {
    setDragTool(XsheetGUI::DragTool::makeDragAndDropDataTool(m_viewer));
    m_viewer->dragToolClick(e);
    e->acceptProposedAction();
  }
}

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

void CellArea::dragLeaveEvent(QDragLeaveEvent *e) {
  if (!getDragTool()) return;
  m_viewer->dragToolLeave(e);
}

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

void CellArea::dragMoveEvent(QDragMoveEvent *e) {
  if (!getDragTool()) return;
  m_viewer->dragToolDrag(e);
  e->acceptProposedAction();
}

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

void CellArea::dropEvent(QDropEvent *e) {
  if (!getDragTool()) return;
  m_viewer->dragToolRelease(e);
  if (e->source() == this) {
    e->setDropAction(Qt::MoveAction);
    e->accept();
  } else
    e->acceptProposedAction();
}

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

bool CellArea::event(QEvent *event) {
  QEvent::Type type = event->type();
  if (type == QEvent::ToolTip) {
    if (!m_tooltip.isEmpty())
      QToolTip::showText(mapToGlobal(m_pos), m_tooltip);
    else
      QToolTip::hideText();
  }
  if (type == QEvent::WindowDeactivate && m_isMousePressed) {
    QMouseEvent e(QEvent::MouseButtonRelease, m_pos, Qt::LeftButton,
                  Qt::NoButton, Qt::NoModifier);
    mouseReleaseEvent(&e);
  }
  return QWidget::event(event);
}
//-----------------------------------------------------------------------------

void CellArea::onControlPressed(bool pressed) {
  isCtrlPressed = pressed;
  update();
}

//-----------------------------------------------------------------------------
void CellArea::createCellMenu(QMenu &menu, bool isCellSelected) {
  CommandManager *cmdManager = CommandManager::instance();

  bool soundCellsSelected = m_viewer->areSoundCellsSelected();

  if (m_viewer->areSoundTextCellsSelected()) return;  // Magpies stop here

  menu.addSeparator();

  if (!soundCellsSelected) {
    menu.addAction(cmdManager->getAction(MI_LoadLevel));
    menu.addAction(cmdManager->getAction(MI_NewLevel));
    menu.addSeparator();
  }

  if (isCellSelected) {
    if (!soundCellsSelected) {
      menu.addAction(cmdManager->getAction(MI_LevelSettings));

      //- force reframe
      QMenu *reframeSubMenu = new QMenu(tr("Reframe"), this);
      {
        reframeSubMenu->addAction(cmdManager->getAction(MI_Reframe1));
        reframeSubMenu->addAction(cmdManager->getAction(MI_Reframe2));
        reframeSubMenu->addAction(cmdManager->getAction(MI_Reframe3));
        reframeSubMenu->addAction(cmdManager->getAction(MI_Reframe4));
      }
      menu.addMenu(reframeSubMenu);

      QMenu *stepSubMenu = new QMenu(tr("Step"), this);
      {
        stepSubMenu->addAction(cmdManager->getAction(MI_Step2));
        stepSubMenu->addAction(cmdManager->getAction(MI_Step3));
        stepSubMenu->addAction(cmdManager->getAction(MI_Step4));
        stepSubMenu->addAction(cmdManager->getAction(MI_ResetStep));
        stepSubMenu->addAction(cmdManager->getAction(MI_IncreaseStep));
        stepSubMenu->addAction(cmdManager->getAction(MI_DecreaseStep));
      }
      menu.addMenu(stepSubMenu);
      QMenu *eachSubMenu = new QMenu(tr("Each"), this);
      {
        eachSubMenu->addAction(cmdManager->getAction(MI_Each2));
        eachSubMenu->addAction(cmdManager->getAction(MI_Each3));
        eachSubMenu->addAction(cmdManager->getAction(MI_Each4));
      }
      menu.addMenu(eachSubMenu);

      menu.addSeparator();

      menu.addAction(cmdManager->getAction(MI_Reverse));
      menu.addAction(cmdManager->getAction(MI_Swing));
      menu.addAction(cmdManager->getAction(MI_Random));
      menu.addAction(cmdManager->getAction(MI_Dup));

      menu.addAction(cmdManager->getAction(MI_Rollup));
      menu.addAction(cmdManager->getAction(MI_Rolldown));
      menu.addAction(cmdManager->getAction(MI_TimeStretch));
      menu.addSeparator();
      menu.addAction(cmdManager->getAction(MI_Autorenumber));
    }
    menu.addAction(cmdManager->getAction(MI_ReplaceLevel));

    menu.addAction(cmdManager->getAction(MI_ReplaceParentDirectory));

    {
      // replace with another level in scene cast
      std::vector<TXshLevel *> levels;
      TApp::instance()
          ->getCurrentScene()
          ->getScene()
          ->getLevelSet()
          ->listLevels(levels);
      if (!levels.empty()) {
        QMenu *replaceMenu = menu.addMenu(tr("Replace"));
        connect(replaceMenu, SIGNAL(triggered(QAction *)), this,
                SLOT(onReplaceByCastedLevel(QAction *)));
        for (int i = 0; i < (int)levels.size(); i++) {
          if (!levels[i]->getSimpleLevel() && !levels[i]->getChildLevel())
            continue;

          if (levels[i]->getChildLevel() &&
              !TApp::instance()->getCurrentXsheet()->getXsheet()->isLevelUsed(
                  levels[i]))
            continue;

          QString tmpLevelName = QString::fromStdWString(levels[i]->getName());
          QAction *tmpAction   = new QAction(tmpLevelName, replaceMenu);
          tmpAction->setData(tmpLevelName);
          replaceMenu->addAction(tmpAction);
        }
      }
    }

    if (!soundCellsSelected) {
      if (selectionContainTlvImage(m_viewer->getCellSelection(),
                                   m_viewer->getXsheet()))
        menu.addAction(cmdManager->getAction(MI_RevertToCleanedUp));
      if (selectionContainLevelImage(m_viewer->getCellSelection(),
                                     m_viewer->getXsheet()))
        menu.addAction(cmdManager->getAction(MI_RevertToLastSaved));
      menu.addAction(cmdManager->getAction(MI_SetKeyframes));
    }
    menu.addSeparator();

    TXshSimpleLevel *sl = TApp::instance()->getCurrentLevel()->getSimpleLevel();
    if (sl || soundCellsSelected)
      menu.addAction(cmdManager->getAction(MI_FileInfo));
    if (sl && (sl->getType() & LEVELCOLUMN_XSHLEVEL))
      menu.addAction(cmdManager->getAction(MI_ViewFile));

    menu.addSeparator();
    if (selectionContainRasterImage(m_viewer->getCellSelection(),
                                    m_viewer->getXsheet())) {
      menu.addAction(cmdManager->getAction(MI_AdjustLevels));
      menu.addAction(cmdManager->getAction(MI_LinesFade));
      menu.addAction(cmdManager->getAction(MI_BrightnessAndContrast));
      menu.addAction(cmdManager->getAction(MI_Antialias));
      menu.addAction(cmdManager->getAction(MI_CanvasSize));
    } else if (selectionContainTlvImage(m_viewer->getCellSelection(),
                                        m_viewer->getXsheet()))
      menu.addAction(cmdManager->getAction(MI_CanvasSize));
  }
  menu.addSeparator();
  if (!soundCellsSelected)
    menu.addAction(cmdManager->getAction(MI_ImportMagpieFile));
}
//-----------------------------------------------------------------------------
/*! replace level with another level in the cast
 */
void CellArea::onReplaceByCastedLevel(QAction *action) {
  std::wstring levelName = action->data().toString().toStdWString();
  TXshLevel *level =
      TApp::instance()->getCurrentScene()->getScene()->getLevelSet()->getLevel(
          levelName);

  if (!level) return;
  TCellSelection *cellSelection = m_viewer->getCellSelection();
  if (cellSelection->isEmpty()) return;
  int r0, c0, r1, c1;
  cellSelection->getSelectedCells(r0, c0, r1, c1);

  bool changed = false;

  TUndoManager *um = TUndoManager::manager();
  um->beginBlock();

  TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
  for (int c = c0; c <= c1; c++) {
    for (int r = r0; r <= r1; r++) {
      TXshCell cell = xsh->getCell(r, c);
      if (!cell.m_level.getPointer() || cell.m_level.getPointer() == level)
        continue;

      TXshCell oldCell = cell;

      cell.m_level = TXshLevelP(level);
      xsh->setCell(r, c, cell);

      RenameCellUndo *undo = new RenameCellUndo(r, c, oldCell, cell);
      um->add(undo);

      changed = true;
    }
  }

  if (changed) TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();

  um->endBlock();
}

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

void CellArea::createKeyMenu(QMenu &menu) {
  CommandManager *cmdManager = CommandManager::instance();
  menu.addAction(cmdManager->getAction(MI_SelectRowKeyframes));
  menu.addAction(cmdManager->getAction(MI_SelectColumnKeyframes));
  menu.addAction(cmdManager->getAction(MI_SelectAllKeyframes));
  menu.addAction(cmdManager->getAction(MI_SelectAllKeyframesNotBefore));
  menu.addAction(cmdManager->getAction(MI_SelectAllKeyframesNotAfter));
  menu.addAction(cmdManager->getAction(MI_SelectPreviousKeysInColumn));
  menu.addAction(cmdManager->getAction(MI_SelectFollowingKeysInColumn));
  menu.addAction(cmdManager->getAction(MI_SelectPreviousKeysInRow));
  menu.addAction(cmdManager->getAction(MI_SelectFollowingKeysInRow));
  menu.addAction(cmdManager->getAction(MI_InvertKeyframeSelection));
  menu.addSeparator();
  menu.addAction(cmdManager->getAction(MI_Cut));
  menu.addAction(cmdManager->getAction(MI_Copy));
  menu.addAction(cmdManager->getAction(MI_Paste));
  menu.addAction(cmdManager->getAction(MI_Clear));
  menu.addSeparator();
  menu.addAction(cmdManager->getAction(MI_OpenFunctionEditor));
}

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

void CellArea::createKeyLineMenu(QMenu &menu, int row, int col) {
  TStageObject *pegbar =
      m_viewer->getXsheet()->getStageObject(m_viewer->getObjectId(col));
  CommandManager *cmdManager = CommandManager::instance();
  int r0, r1, rh0, rh1;
  double e0, e1;
  if (pegbar->getKeyframeSpan(row, r0, e0, r1, e1) &&
      getEaseHandles(r0, r1, e0, e1, rh0, rh1)) {
    menu.addAction(cmdManager->getAction(MI_SetAcceleration));
    menu.addAction(cmdManager->getAction(MI_SetDeceleration));
    menu.addAction(cmdManager->getAction(MI_SetConstantSpeed));
  } else {
    // Se le due chiavi non sono linear aggiungo il comando ResetInterpolation
    bool isR0FullK = pegbar->isFullKeyframe(r0);
    bool isR1FullK = pegbar->isFullKeyframe(r1);
    TDoubleKeyframe::Type r0Type =
        pegbar->getParam(TStageObject::T_X)->getKeyframeAt(r0).m_type;
    TDoubleKeyframe::Type r1Type =
        pegbar->getParam(TStageObject::T_X)->getKeyframeAt(r1).m_prevType;
    if (isGlobalKeyFrameWithSameTypeDiffFromLinear(pegbar, r0) &&
        isGlobalKeyFrameWithSamePrevTypeDiffFromLinear(pegbar, r1))
      menu.addAction(cmdManager->getAction(MI_ResetInterpolation));
  }
#ifdef LINETEST
  menu.addSeparator();
  int paramStep             = getParamStep(pegbar, r0);
  QActionGroup *actionGroup = new QActionGroup(this);
  int i;
  for (i = 1; i < 4; i++) {
    QAction *act = new QAction(QString("Step ") + QString::number(i), this);
    if (paramStep == i) act->setEnabled(false);
    QList<QVariant> list;
    list.append(QVariant(i));
    list.append(QVariant(r0));
    list.append(QVariant(col));
    act->setData(QVariant(list));
    actionGroup->addAction(act);
    menu.addAction(act);
  }
  connect(actionGroup, SIGNAL(triggered(QAction *)), this,
          SLOT(onStepChanged(QAction *)));
#endif
}

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

void CellArea::createNoteMenu(QMenu &menu) {
  QAction *openAct   = menu.addAction(tr("Open Memo"));
  QAction *deleteAct = menu.addAction(tr("Delete Memo"));
  bool ret = connect(openAct, SIGNAL(triggered()), this, SLOT(openNote()));
  ret =
      ret && connect(deleteAct, SIGNAL(triggered()), this, SLOT(deleteNote()));
}

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

void CellArea::openNote() {
  TXshNoteSet *notes = m_viewer->getXsheet()->getNotes();
  int currentIndex   = m_viewer->getCurrentNoteIndex();
  m_viewer->getNotesWidget().at(currentIndex)->openNotePopup();
}

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

void CellArea::deleteNote() {
  TXshNoteSet *notes = m_viewer->getXsheet()->getNotes();
  int currentIndex   = m_viewer->getCurrentNoteIndex();
  notes->removeNote(currentIndex);
  m_viewer->discardNoteWidget();
}

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

void CellArea::onStepChanged(QAction *act) {
#ifdef LINETEST
  QList<QVariant> list = act->data().toList();
  int step             = list.at(0).toInt();
  int frame            = list.at(1).toInt();
  int col              = list.at(2).toInt();

  // Siamo in LineTest il keyframe è globale quindi basta calcolare l'indice
  // del primo parametro!!!!
  TUndoManager::manager()->beginBlock();
  TStageObject *stageObject =
      m_viewer->getXsheet()->getStageObject(m_viewer->getObjectId(col));
  TDoubleParam *param = stageObject->getParam(TStageObject::T_Angle);
  int keyFrameIndex   = param->getClosestKeyframe(frame);
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_Angle));
  setParamStep(keyFrameIndex, step, stageObject->getParam(TStageObject::T_X));
  setParamStep(keyFrameIndex, step, stageObject->getParam(TStageObject::T_Y));
  setParamStep(keyFrameIndex, step, stageObject->getParam(TStageObject::T_Z));
  setParamStep(keyFrameIndex, step, stageObject->getParam(TStageObject::T_SO));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_ScaleX));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_ScaleY));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_Scale));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_Path));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_ShearX));
  setParamStep(keyFrameIndex, step,
               stageObject->getParam(TStageObject::T_ShearY));
  TUndoManager::manager()->endBlock();
#endif
}

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

}  // namespace XsheetGUI;