tahoma2d/toonz/sources/toonzqt/functionsheet.cpp
Jeremy Bullock 4ce9539416 Function Editor Display Options (#1649)
* Function Editor Display Options
2018-03-22 16:32:19 +09:00

1159 lines
40 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "toonzqt/functionsheet.h"
// TnzQt includes
#include "toonzqt/gutil.h"
#include "toonzqt/functionviewer.h"
// TnzLib includes
#include "toonz/tframehandle.h"
#include "toonz/doubleparamcmd.h"
#include "toonz/preferences.h"
#include "toonz/toonzfolders.h"
// TnzBase includes
#include "tunit.h"
#include "../toonz/menubarcommandids.h"
// Qt includes
#include <QPainter>
#include <QGridLayout>
#include <QPaintEvent>
#include <QMenu>
#include <QApplication> //for drag&drop
#include <QDrag>
//********************************************************************************
// Local namespace stuff
//********************************************************************************
namespace {
const int cColumnDragHandleWidth = 8;
const int cGroupShortNameY =
0; //!< Column header's y pos for channel groups' short name tabs
const int cGroupLongNameY = 27; //!< Same for its long name tabs
const int cChannelNameY = 50; //!< Column header's y pos of channel name tabs,
//! up to the widget's height
const int cColHeadersEndY = 87; //!< End of column headers y pos
} // namespace
//********************************************************************************
// Function Sheet Tools
//********************************************************************************
/*--- NumericalColumnsのセグメントの左側のバーをクリックしたとき ---*/
class MoveChannelsDragTool final : public Spreadsheet::DragTool {
FunctionSheet *m_sheet;
std::vector<KeyframeSetter *> m_setters;
int m_oldRow;
QRect m_selectedCells;
int m_firstKeyframeRow;
public:
MoveChannelsDragTool(FunctionSheet *sheet)
: m_sheet(sheet), m_firstKeyframeRow(-1) {}
void click(int row, int col, QMouseEvent *e) override {
m_firstKeyframeRow = -1;
FunctionTreeModel::Channel *channel = m_sheet->getChannel(col);
if (!channel) return;
TDoubleParam *curve = channel->getParam();
int k0 = -1, k1 = -1;
if (curve->isKeyframe(row))
k0 = k1 = curve->getClosestKeyframe(row);
else {
k0 = curve->getPrevKeyframe(row);
k1 = curve->getNextKeyframe(row);
}
// return if clicking outside of the segments
if (k0 < 0 || k1 < 0) return;
int r0 = tround(curve->keyframeIndexToFrame(k0));
int r1 = tround(curve->keyframeIndexToFrame(k1));
if (m_sheet->isSelectedCell(row, col)) {
m_selectedCells = m_sheet->getSelectedCells();
m_selectedCells.setTop(r0);
m_selectedCells.setBottom(r1);
} else
m_selectedCells = QRect(col, r0, 1, r1 - r0 + 1);
m_sheet->selectCells(m_selectedCells);
/*---
シンプルに左のバーをクリックした場合はcolは1つだけ。
すでに複数Columnが選択されている上でその選択範囲内のセルの左バーをクリックした場合は
Column幅は選択範囲に順ずるようになる。高さ(row)はクリックしたセグメントと同じになる。
---*/
/*--- セグメントごとドラッグに備えてKeyFrameを格納する ---*/
for (int col = m_selectedCells.left(); col <= m_selectedCells.right();
++col) {
TDoubleParam *curve = m_sheet->getCurve(col);
if (!curve) continue;
KeyframeSetter *setter = new KeyframeSetter(curve);
for (int k = 0; k < curve->getKeyframeCount(); k++) {
int row = (int)curve->keyframeIndexToFrame(k);
if (r0 <= row && row <= r1) {
if (m_firstKeyframeRow < 0 || row < m_firstKeyframeRow)
m_firstKeyframeRow = row;
setter->selectKeyframe(k);
}
}
m_setters.push_back(setter);
}
m_oldRow = row;
}
void drag(int row, int col, QMouseEvent *e) override {
int d = row - m_oldRow;
m_oldRow = row;
if (d + m_firstKeyframeRow < 0) d = -m_firstKeyframeRow;
m_firstKeyframeRow += d;
for (int i = 0; i < (int)m_setters.size(); i++)
m_setters[i]->moveKeyframes(d, 0.0);
m_selectedCells.translate(0, d);
m_sheet->selectCells(m_selectedCells);
}
void release(int row, int col, QMouseEvent *e) override {
for (int i = 0; i < (int)m_setters.size(); i++) delete m_setters[i];
m_setters.clear();
}
};
//-----------------------------------------------------------------------------
/*--- NumericalColumnsのセル部分をクリックしたとき ---*/
class FunctionSheetSelectionTool final : public Spreadsheet::DragTool {
int m_firstRow, m_firstCol;
FunctionSheet *m_sheet;
public:
FunctionSheetSelectionTool(FunctionSheet *sheet)
: m_sheet(sheet), m_firstRow(-1), m_firstCol(-1) {}
void click(int row, int col, QMouseEvent *e) override {
if (0 != (e->modifiers() & Qt::ShiftModifier) &&
!m_sheet->getSelectedCells().isEmpty()) {
QRect selectedCells = m_sheet->getSelectedCells();
if (col < selectedCells.center().x()) {
m_firstCol = selectedCells.right();
selectedCells.setLeft(col);
} else {
m_firstCol = selectedCells.left();
selectedCells.setRight(col);
}
if (row < selectedCells.center().y()) {
m_firstRow = selectedCells.bottom();
selectedCells.setTop(row);
} else {
m_firstRow = selectedCells.top();
selectedCells.setBottom(row);
}
m_sheet->selectCells(selectedCells);
} else {
// regular click, no shift
m_firstCol = col;
m_firstRow = row;
QRect selectedCells(col, row, 1, 1);
m_sheet->selectCells(selectedCells);
}
}
void drag(int row, int col, QMouseEvent *e) override {
if (row < 0) row = 0;
if (col < 0) col = 0;
int r0 = qMin(row, m_firstRow);
int r1 = qMax(row, m_firstRow);
int c0 = qMin(col, m_firstCol);
int c1 = qMax(col, m_firstCol);
QRect selectedCells(c0, r0, c1 - c0 + 1, r1 - r0 + 1);
m_sheet->selectCells(selectedCells);
}
void release(int row, int col, QMouseEvent *e) override {
if (row == m_firstRow && col == m_firstCol) {
if (Preferences::instance()->isMoveCurrentEnabled())
m_sheet->setCurrentFrame(row);
FunctionTreeModel::Channel *channel = m_sheet->getChannel(col);
if (channel) channel->setIsCurrent(true);
}
}
};
//********************************************************************************
// FunctionSheetRowViewer implementation
//********************************************************************************
FunctionSheetRowViewer::FunctionSheetRowViewer(FunctionSheet *parent)
: Spreadsheet::RowPanel(parent), m_sheet(parent) {
setMinimumSize(QSize(100, 100));
setMouseTracking(true);
setFocusPolicy(Qt::NoFocus);
}
//-----------------------------------------------------------------------------
void FunctionSheetRowViewer::paintEvent(QPaintEvent *e) {
// calls GenericPanel's event
Spreadsheet::RowPanel::paintEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetRowViewer::mousePressEvent(QMouseEvent *e) {
// calls GenericPanel's event
Spreadsheet::RowPanel::mousePressEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetRowViewer::mouseMoveEvent(QMouseEvent *e) {
// calls GenericPanel's event
Spreadsheet::RowPanel::mouseMoveEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetRowViewer::mouseReleaseEvent(QMouseEvent *e) {
Spreadsheet::RowPanel::mouseReleaseEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetRowViewer::contextMenuEvent(QContextMenuEvent *event) {
QMenu *menu = new QMenu(this);
CommandManager *cmdManager = CommandManager::instance();
menu->addAction(cmdManager->getAction(MI_InsertSceneFrame));
menu->addAction(cmdManager->getAction(MI_RemoveSceneFrame));
menu->addAction(cmdManager->getAction(MI_InsertGlobalKeyframe));
menu->addAction(cmdManager->getAction(MI_RemoveGlobalKeyframe));
menu->exec(event->globalPos());
}
//********************************************************************************
// FunctionSheetColumnHeadViewer implementation
//********************************************************************************
FunctionSheetColumnHeadViewer::FunctionSheetColumnHeadViewer(
FunctionSheet *parent)
: Spreadsheet::ColumnPanel(parent), m_sheet(parent), m_draggingChannel(0) {
setMouseTracking(true); // for updating the tooltip
setFocusPolicy(Qt::NoFocus);
}
//-----------------------------------------------------------------------------
void FunctionSheetColumnHeadViewer::paintEvent(QPaintEvent *e) {
QPainter painter(this);
QRect toBeUpdated = e->rect();
painter.setClipRect(toBeUpdated);
// visible columns range
CellRange visible = getViewer()->xyRectToRange(toBeUpdated);
int c0 = visible.from().layer();
int c1 = visible.to().layer();
if (c0 > c1) return;
int n = c1 - c0 + 1 + 2;
FunctionTreeModel::ChannelGroup *currentGroup = 0;
/*--- Display range + right and left 1 column range ChannelGroup. If there is
* none, put 0
* ---*/
std::vector<FunctionTreeModel::ChannelGroup *> channelGroups(n);
for (int i = 0; i < n; ++i) {
channelGroups[i] = 0;
int c = c0 - 1 + i;
if (c < 0) continue;
FunctionTreeModel::Channel *channel = m_sheet->getChannel(c);
if (!channel) continue;
channelGroups[i] = channel->getChannelGroup();
if (!currentGroup && channel->isCurrent()) currentGroup = channelGroups[i];
}
int y0 = 0;
int y1 = 17; // needs work
int y2 = 34;
int y3 = 53;
/*--- Fill header with background color ---*/
for (int c = c0; c <= c1; c++) {
FunctionTreeModel::Channel *channel = m_sheet->getChannel(c);
if (!channel) continue;
int x0 = getViewer()->columnToX(c);
int x1 = getViewer()->columnToX(c + 1) - 1;
// Column Width
int width = x1 - x0 + 1;
painter.fillRect(x0, y0, width, y3 - y0, getViewer()->getBGColor());
}
for (int c = c0; c <= c1; ++c) {
FunctionTreeModel::Channel *channel = m_sheet->getChannel(c);
if (!channel) continue;
int i = c - c0 + 1;
/*---- Channel Column of the current Column and the preceding and following
* Columns ---*/
FunctionTreeModel::ChannelGroup *prevGroup = channelGroups[c - c0],
*group = channelGroups[c - c0 + 1],
*nextGroup = channelGroups[c - c0 + 2];
/*---- If the group is different from the before and after, flags are set
* respectively ---*/
bool firstGroupColumn = prevGroup != group;
bool lastGroupColumn = nextGroup != group;
/*--- The left and right coordinates of the current column ---*/
int x0 = getViewer()->columnToX(c);
int x1 = getViewer()->columnToX(c + 1) - 1;
// Column width
int width = x1 - x0 + 1;
QRect selectedRect = m_sheet->getSelectedCells();
bool isSelected =
(selectedRect.left() <= c && c <= selectedRect.right()) ? true : false;
// paint with light color if selected
if (isSelected)
painter.fillRect(x0, y1, width, y3 - y1,
getViewer()->getCurrentRowBgColor());
// draw horizonntal lines
painter.setPen(QPen(getViewer()->getColumnHeaderBorderColor(), 3));
painter.drawLine(x0, y0, x1, y0);
painter.setPen(getViewer()->getColumnHeaderBorderColor());
painter.drawLine(x0, y1, x1, y1);
// draw vertical bar
painter.fillRect(x0, y1, 6, y3 - y1,
getViewer()->getColumnHeaderBorderColor());
if (firstGroupColumn)
painter.fillRect(x0, y0, 6, y1 - y0,
getViewer()->getColumnHeaderBorderColor());
// channel name
painter.setPen(getViewer()->getTextColor());
if (channel->isCurrent())
painter.setPen(getViewer()->getSelectedColumnTextColor());
QString text = channel->getShortName();
int d = 8;
painter.drawText(x0 + d, y1, width - d, y3 - y1 + 1,
Qt::TextWrapAnywhere | Qt::AlignLeft | Qt::AlignVCenter,
text);
// group name
if (firstGroupColumn) {
int tmpwidth = (lastGroupColumn) ? width : width * 2;
painter.setPen(getViewer()->getTextColor());
if (group == currentGroup)
painter.setPen(getViewer()->getSelectedColumnTextColor());
text = group->getShortName();
painter.drawText(x0 + d, y0, tmpwidth - d, y1 - y0 + 1,
Qt::AlignLeft | Qt::AlignVCenter, text);
}
}
}
//-----------------------------------------------------------------------------
/*! update tooltips
*/
void FunctionSheetColumnHeadViewer::mouseMoveEvent(QMouseEvent *e) {
if ((e->buttons() & Qt::MidButton) && m_draggingChannel &&
(e->pos() - m_dragStartPosition).manhattanLength() >=
QApplication::startDragDistance()) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(m_draggingChannel->getExprRefName());
drag->setMimeData(mimeData);
static const QPixmap cursorPixmap(":Resources/dragcursor_exp_text.png");
drag->setDragCursor(cursorPixmap, Qt::MoveAction);
Qt::DropAction dropAction = drag->exec();
return;
}
// get the column under the cursor
int col = getViewer()->xyToPosition(e->pos()).layer();
FunctionTreeModel::Channel *channel = m_sheet->getChannel(col);
if (!channel) {
setToolTip(QString(""));
} else
setToolTip(channel->getExprRefName());
}
//-----------------------------------------------------------------------------
void FunctionSheetColumnHeadViewer::mousePressEvent(QMouseEvent *e) {
QPoint pos = e->pos();
int currentC = getViewer()->xyToPosition(pos).layer();
FunctionTreeModel::Channel *channel;
for (int c = 0; c <= m_sheet->getChannelCount(); c++) {
channel = m_sheet->getChannel(c);
if (!channel || c != currentC) continue;
break;
}
if (channel && e->button() == Qt::MidButton) {
m_draggingChannel = channel;
m_dragStartPosition = e->pos();
return;
} else if (channel)
channel->setIsCurrent(true);
m_draggingChannel = 0;
if (!channel) return;
// Open folder
FunctionTreeModel::ChannelGroup *channelGroup = channel->getChannelGroup();
if (!channelGroup->isOpen())
channelGroup->getModel()->setExpandedItem(channelGroup->createIndex(),
true);
// Select all segment
std::set<double> frames;
channel->getParam()->getKeyframes(frames);
QRect rect(0, 0, 0, 0);
if (!frames.empty()) rect = QRect(currentC, 0, 1, (*frames.rbegin()) + 1);
getViewer()->selectCells(rect);
}
//-----------------------------------------------------------------------------
void FunctionSheetColumnHeadViewer::contextMenuEvent(QContextMenuEvent *ce) {
// First, select column under cursor
const QPoint &pos = ce->pos();
int cursorCol = getViewer()->xyToPosition(pos).layer();
if (cursorCol < 0 || cursorCol >= m_sheet->getChannelCount()) return;
FunctionTreeModel::Channel *channel = m_sheet->getChannel(cursorCol);
if (!channel) return;
// Ok, now let's summon a context menu with appropriate options
FunctionViewer *fv = m_sheet->getViewer();
if (!fv) {
assert(fv);
return;
}
const QPoint &globalPos = mapToGlobal(pos);
if (pos.y() >= cChannelNameY)
fv->openContextMenu(channel, globalPos);
else {
FunctionTreeModel::ChannelGroup *group = channel->getChannelGroup();
// In this case, commands are different from the tree view. Rather than
// showing in the tree,
// channels get ACTIVATED
QMenu menu;
QAction showAnimatedOnly(FunctionTreeView::tr("Show Animated Only"), 0);
QAction showAll(FunctionTreeView::tr("Show All"), 0);
menu.addAction(&showAnimatedOnly);
menu.addAction(&showAll);
// execute menu
QAction *action = menu.exec(globalPos);
if (action != &showAll && action != &showAnimatedOnly) return;
// Process action
if (action == &showAll) {
int c, cCount = group->getChildCount();
for (c = 0; c != cCount; ++c) {
FunctionTreeModel::Channel *channel =
dynamic_cast<FunctionTreeModel::Channel *>(group->getChild(c));
if (channel && !channel->isHidden()) channel->setIsActive(true);
}
} else if (action == &showAnimatedOnly) {
int c, cCount = group->getChildCount();
for (c = 0; c != cCount; ++c) {
FunctionTreeModel::Channel *channel =
dynamic_cast<FunctionTreeModel::Channel *>(group->getChild(c));
if (channel && !channel->isHidden())
channel->setIsActive(channel->isAnimated());
}
}
fv->update();
}
}
//********************************************************************************
// FunctionSheetCellViewer implementation
//********************************************************************************
FunctionSheetCellViewer::FunctionSheetCellViewer(FunctionSheet *parent)
: Spreadsheet::CellPanel(parent)
, m_sheet(parent)
, m_editRow(0)
, m_editCol(0) {
m_lineEdit = new DVGui::LineEdit(this);
// lineEdit->setGeometry(10,10,100,30);
m_lineEdit->hide();
bool ret = connect(m_lineEdit, SIGNAL(editingFinished()), this,
SLOT(onCellEditorEditingFinished()));
ret = ret && connect(m_lineEdit, SIGNAL(mouseMoved(QMouseEvent *)), this,
SLOT(onMouseMovedInLineEdit(QMouseEvent *)));
assert(ret);
setMouseTracking(true);
setFocusProxy(m_lineEdit);
}
//-----------------------------------------------------------------------------
/*! Called when the cell panel is left/right-clicked
*/
Spreadsheet::DragTool *FunctionSheetCellViewer::createDragTool(QMouseEvent *e) {
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
bool isEmpty = true;
TDoubleParam *curve = m_sheet->getCurve(col);
if (curve) {
int kCount = curve->getKeyframeCount();
if (kCount > 0) {
int row0 = (int)curve->keyframeIndexToFrame(0);
int row1 = (int)curve->keyframeIndexToFrame(kCount - 1);
isEmpty = row < row0 || row > row1;
}
}
if (!isEmpty) {
int x = e->pos().x() - getViewer()->columnToX(col);
if (0 <= x && x < cColumnDragHandleWidth + 9)
return new MoveChannelsDragTool(m_sheet);
}
return new FunctionSheetSelectionTool(m_sheet);
// return Spreadsheet::CellPanel::createDragTool(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::drawCells(QPainter &painter, int r0, int c0,
int r1, int c1) {
// key frames
QColor KeyFrameColor = getViewer()->getKeyFrameColor();
QColor KeyFrameBorderColor = getViewer()->getKeyFrameBorderColor();
QColor SelectedKeyFrameColor = getViewer()->getSelectedKeyFrameColor();
// inbetween
QColor InBetweenColor = getViewer()->getInBetweenColor();
QColor InBetweenBorderColor = getViewer()->getInBetweenBorderColor();
QColor SelectedInBetweenColor = getViewer()->getSelectedInBetweenColor();
// empty cells
QColor SelectedEmptyColor = getViewer()->getSelectedEmptyColor();
// empty cells in scene frame range
QColor SelectedSceneRangeEmptyColor =
getViewer()->getSelectedSceneRangeEmptyColor();
// top and bottom pos
int y0 = getViewer()->rowToY(r0);
int y1 = getViewer()->rowToY(r1 + 1) - 1;
for (int c = c0; c <= c1; c++) {
TDoubleParam *curve = m_sheet->getCurve(c);
/*--- もしカラムcにパラメータが無ければcurveにはが返る ---*/
if (!curve) continue;
// left and right pos
int x0 = getViewer()->columnToX(c);
int x1 = getViewer()->columnToX(c + 1) - 1;
// find the curve keyframe range
int kr0 = 0, kr1 = -1;
int kCount = curve->getKeyframeCount();
if (kCount > 0) {
kr0 = curve->keyframeIndexToFrame(0);
kr1 = curve->keyframeIndexToFrame(kCount - 1);
}
// get the unit
TMeasure *measure = curve->getMeasure();
const TUnit *unit = measure ? measure->getCurrentUnit() : 0;
// draw each cell
for (int row = r0; row <= r1; row++) {
int ya = m_sheet->rowToY(row);
int yb = m_sheet->rowToY(row + 1) - 1;
bool isSelected = getViewer()->isSelectedCell(row, c);
double value = curve->getValue(row);
if (unit) value = unit->convertTo(value);
QRect cellRect(x0, ya, x1 - x0 + 1, yb - ya + 1);
QRect borderRect(x0, ya, 7, yb - ya + 1);
QColor cellColor, borderColor;
/*--- キーフレーム間の範囲だけ色をつける ---*/
if (kr0 <= row && row <= kr1) {
if (curve->isKeyframe(row)) {
cellColor = (isSelected) ? SelectedKeyFrameColor : KeyFrameColor;
borderColor = KeyFrameBorderColor;
} else {
cellColor = (isSelected) ? SelectedInBetweenColor : InBetweenColor;
borderColor = InBetweenBorderColor;
}
painter.setPen(Qt::NoPen);
painter.fillRect(cellRect, cellColor);
painter.fillRect(borderRect, borderColor);
// display whether segment are Linked
if (curve->isKeyframe(row)) {
TDoubleKeyframe kf = curve->getKeyframeAt(row);
// if the segments are NOT linked, then cut off the side bar
if (!kf.m_linkedHandles) {
int rowCenterPos = (ya + yb) / 2;
QPoint points[4] = {
QPoint(x0, rowCenterPos), QPoint(x0 + 7, rowCenterPos + 3),
QPoint(x0 + 7, rowCenterPos - 3), QPoint(x0, rowCenterPos)};
QBrush oldBrush = painter.brush();
painter.setBrush(QBrush(cellColor));
painter.drawPolygon(points, 4);
painter.setBrush(oldBrush);
}
}
// draw cell value
painter.setPen(getViewer()->getTextColor());
/*--- 整数から小数点以下3桁以内の場合はそれ以降の0000を描かない ---*/
QString text;
double thousandValue = value * 1000.0;
if (areAlmostEqual(thousandValue, (double)tround(thousandValue),
0.0001)) {
text = QString::number(value, 'f', 3);
while (text.endsWith("0")) {
text.chop(1);
if (text.endsWith(".")) {
text.chop(1);
break;
}
}
} else {
text = QString::number(value, 'f', 4);
text.truncate(5);
text.append("~");
}
QString fontName = Preferences::instance()->getInterfaceFont();
if (fontName == "") {
#ifdef _WIN32
fontName = "Arial";
#else
fontName = "Helvetica";
#endif
}
static QFont font(fontName, -1, QFont::Bold);
font.setPixelSize(12);
painter.setFont(font);
painter.drawText(cellRect.adjusted(10, 0, 0, 0),
Qt::AlignVCenter | Qt::AlignLeft, text);
}
// empty and selected cell
else if (isSelected) {
int rowCount = getViewer()->getRowCount();
cellColor = (row >= rowCount) ? SelectedEmptyColor
: SelectedSceneRangeEmptyColor;
painter.setPen(Qt::NoPen);
painter.fillRect(cellRect, cellColor);
}
}
}
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::mouseDoubleClickEvent(QMouseEvent *e) {
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
int x0, y0, x1, y1;
x0 = getViewer()->columnToX(col);
x1 = getViewer()->columnToX(col + 1) - 1;
y0 = getViewer()->rowToY(row);
y1 = getViewer()->rowToY(row + 1) - 1;
m_editRow = row;
m_editCol = col;
TDoubleParam *curve = m_sheet->getCurve(col);
if (curve) {
double v = curve->getValue(row);
TMeasure *measure = curve->getMeasure();
const TUnit *unit = measure ? measure->getCurrentUnit() : 0;
if (unit) v = unit->convertTo(v);
m_currentValue = v;
m_lineEdit->setText(QString::number(v, 'f', 4));
// in order to put the cursor to the left end
m_lineEdit->setSelection(m_lineEdit->text().length(),
-m_lineEdit->text().length());
} else
m_lineEdit->setText("");
QString fontName = Preferences::instance()->getInterfaceFont();
if (fontName == "") {
#ifdef _WIN32
fontName = "Arial";
#else
fontName = "Helvetica";
#endif
}
static QFont font(fontName, 9, QFont::Normal);
m_lineEdit->setFont(font);
m_lineEdit->setGeometry(x0 - 2, y0 - 2, x1 - x0 + 1 + 4,
y1 - y0 + 1 + 4); // x0,y0,x1-x0+1,y0-y1+1);
m_lineEdit->show();
m_lineEdit->raise();
m_lineEdit->setFocus();
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::onCellEditorEditingFinished() {
QString text = m_lineEdit->text();
if (!text.isEmpty() &&
(m_lineEdit->isReturnPressed() || m_lineEdit->getMouseDragEditing())) {
double value = text.toDouble();
TDoubleParam *curve = m_sheet->getCurve(m_editCol);
if (curve) {
TMeasure *measure = curve->getMeasure();
const TUnit *unit = measure ? measure->getCurrentUnit() : 0;
if (unit) value = unit->convertFrom(value);
KeyframeSetter::setValue(curve, m_editRow, value);
}
}
m_lineEdit->hide();
m_lineEdit->clearFocus();
m_sheet->setFocus();
update();
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::mousePressEvent(QMouseEvent *e) {
// escape from the line edit by clicking outside
if (m_lineEdit->isVisible()) {
m_lineEdit->hide();
m_lineEdit->clearFocus();
m_sheet->setFocus();
}
if (e->button() == Qt::LeftButton && e->modifiers() == Qt::ControlModifier) {
mouseDoubleClickEvent(e);
if (m_lineEdit->text() != "") {
m_lineEdit->setMouseDragEditing(true);
m_mouseXPosition = e->x();
}
} else if (e->button() == Qt::LeftButton &&
e->modifiers() == Qt::AltModifier) {
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
TDoubleParam *curve = m_sheet->getCurve(col);
if (curve) {
KeyframeSetter::removeKeyframeAt(curve, row);
}
} else if (e->button() == Qt::LeftButton || e->button() == Qt::MidButton)
Spreadsheet::CellPanel::mousePressEvent(e);
else if (e->button() == Qt::RightButton) {
update();
openContextMenu(e);
}
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::mouseReleaseEvent(QMouseEvent *e) {
if (m_lineEdit->getMouseDragEditing()) {
std::string textValue = m_lineEdit->text().toStdString();
onCellEditorEditingFinished();
m_lineEdit->setMouseDragEditing(false);
} else
Spreadsheet::CellPanel::mouseReleaseEvent(e);
/*
CellPosition cellPosition = getViewer ()->xyToPosition (e->pos ());
int row = cellPosition.frame ();
int col = cellPosition.layer ();
FunctionSheet::DragTool *dragTool = m_sheet->getDragTool();
if(dragTool) dragTool->release(row,col);
m_sheet->setDragTool(0);
*/
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::mouseMoveEvent(QMouseEvent *e) {
if (m_lineEdit->getMouseDragEditing()) {
double newValue = m_currentValue + ((e->x() - m_mouseXPosition) / 2);
m_lineEdit->setText(QString::number(newValue, 'f', 4));
m_updatedValue = newValue;
} else
Spreadsheet::CellPanel::mouseMoveEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheetCellViewer::onMouseMovedInLineEdit(QMouseEvent *event) {
if (m_lineEdit->getMouseDragEditing()) mouseMoveEvent(event);
}
//-----------------------------------------------------------------------------
// TODO: refactor: cfr functionpanel.cpp
void FunctionSheetCellViewer::openContextMenu(QMouseEvent *e) {
struct locals {
static void sheet__setSegmentType(FunctionSelection *selection,
TDoubleParam *curve, int segmentIndex,
TDoubleKeyframe::Type type) {
selection->selectSegment(curve, segmentIndex);
KeyframeSetter setter(curve, segmentIndex);
setter.setType(type);
}
}; // locals
QAction deleteKeyframeAction(tr("Delete Key"), 0);
QAction insertKeyframeAction(tr("Set Key"), 0);
QAction setLinearAction(tr("Linear Interpolation"), 0);
QAction setSpeedInOutAction(tr("Speed In / Speed Out Interpolation"), 0);
QAction setEaseInOutAction(tr("Ease In / Ease Out Interpolation"), 0);
QAction setEaseInOut2Action(tr("Ease In / Ease Out (%) Interpolation"), 0);
QAction setExponentialAction(tr("Exponential Interpolation"), 0);
QAction setExpressionAction(tr("Expression Interpolation"), 0);
QAction setFileAction(tr("File Interpolation"), 0);
QAction setConstantAction(tr("Constant Interpolation"), 0);
QAction setStep1Action(tr("Step 1"), 0);
QAction setStep2Action(tr("Step 2"), 0);
QAction setStep3Action(tr("Step 3"), 0);
QAction setStep4Action(tr("Step 4"), 0);
CellPosition cellPosition = getViewer()->xyToPosition(e->pos());
int row = cellPosition.frame();
int col = cellPosition.layer();
TDoubleParam *curve = m_sheet->getCurve(col);
if (!curve) return;
bool isEmpty = true;
bool isKeyframe = false;
// find the curve keyframe range
int kCount = curve->getKeyframeCount();
if (kCount > 0) {
if (curve->keyframeIndexToFrame(0) <= row &&
row <= curve->keyframeIndexToFrame(kCount - 1)) {
isEmpty = false;
isKeyframe = curve->isKeyframe(row);
}
}
int kIndex = curve->getPrevKeyframe(row);
// build menu
QMenu menu(0);
if (!isKeyframe) // menu.addAction(&deleteKeyframeAction); else
menu.addAction(&insertKeyframeAction);
if (!isEmpty && !isKeyframe && kIndex >= 0) {
menu.addSeparator();
TDoubleKeyframe kf = curve->getKeyframe(kIndex);
if (kf.m_type != TDoubleKeyframe::Linear) menu.addAction(&setLinearAction);
if (kf.m_type != TDoubleKeyframe::SpeedInOut)
menu.addAction(&setSpeedInOutAction);
if (kf.m_type != TDoubleKeyframe::EaseInOut)
menu.addAction(&setEaseInOutAction);
if (kf.m_type != TDoubleKeyframe::EaseInOutPercentage)
menu.addAction(&setEaseInOut2Action);
if (kf.m_type != TDoubleKeyframe::Exponential)
menu.addAction(&setExponentialAction);
if (kf.m_type != TDoubleKeyframe::Expression)
menu.addAction(&setExpressionAction);
if (kf.m_type != TDoubleKeyframe::File) menu.addAction(&setFileAction);
if (kf.m_type != TDoubleKeyframe::Constant)
menu.addAction(&setConstantAction);
menu.addSeparator();
if (kf.m_step != 1) menu.addAction(&setStep1Action);
if (kf.m_step != 2) menu.addAction(&setStep2Action);
if (kf.m_step != 3) menu.addAction(&setStep3Action);
if (kf.m_step != 4) menu.addAction(&setStep4Action);
}
menu.addSeparator();
CommandManager *cmdManager = CommandManager::instance();
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.addAction(cmdManager->getAction("MI_Insert"));
FunctionSelection *selection = m_sheet->getSelection();
// execute menu
QAction *action = menu.exec(e->globalPos()); // QCursor::pos());
if (action == &deleteKeyframeAction) {
KeyframeSetter::removeKeyframeAt(curve, row);
} else if (action == &insertKeyframeAction) {
KeyframeSetter(curve).createKeyframe(row);
} else if (action == &setLinearAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::Linear);
else if (action == &setSpeedInOutAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::SpeedInOut);
else if (action == &setEaseInOutAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::EaseInOut);
else if (action == &setEaseInOut2Action)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::EaseInOutPercentage);
else if (action == &setExponentialAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::Exponential);
else if (action == &setExpressionAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::Expression);
else if (action == &setFileAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::File);
else if (action == &setConstantAction)
locals::sheet__setSegmentType(selection, curve, kIndex,
TDoubleKeyframe::Constant);
else if (action == &setStep1Action)
KeyframeSetter(curve, kIndex).setStep(1);
else if (action == &setStep2Action)
KeyframeSetter(curve, kIndex).setStep(2);
else if (action == &setStep3Action)
KeyframeSetter(curve, kIndex).setStep(3);
else if (action == &setStep4Action)
KeyframeSetter(curve, kIndex).setStep(4);
update();
}
//********************************************************************************
// FunctionSheetColumnToCurveMapper implementation
//********************************************************************************
class FunctionSheetColumnToCurveMapper final : public ColumnToCurveMapper {
FunctionSheet *m_sheet;
public:
FunctionSheetColumnToCurveMapper(FunctionSheet *sheet) : m_sheet(sheet) {}
TDoubleParam *getCurve(int columnIndex) const override {
FunctionTreeModel::Channel *channel = m_sheet->getChannel(columnIndex);
if (channel)
return channel->getParam();
else
return 0;
}
};
//********************************************************************************
// FunctionSheet implementation
//********************************************************************************
FunctionSheet::FunctionSheet(QWidget *parent, bool isFloating)
: SpreadsheetViewer(parent)
, m_selectedCells()
, m_selection(0)
, m_isFloating(isFloating) {
setColumnsPanel(m_columnHeadViewer = new FunctionSheetColumnHeadViewer(this));
setRowsPanel(m_rowViewer = new FunctionSheetRowViewer(this));
setCellsPanel(m_cellViewer = new FunctionSheetCellViewer(this));
setWindowFlag(Qt::Window);
setColumnCount(20);
setWindowTitle(tr("Function Editor"));
setFocusPolicy(Qt::ClickFocus);
if (m_isFloating) {
// load the dialog size
TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath(mySettingsFileName));
QSettings mySettings(toQString(fp), QSettings::IniFormat);
mySettings.beginGroup("Dialogs");
setGeometry(
mySettings.value("FunctionSpreadsheet", QRect(500, 500, 400, 300))
.toRect());
mySettings.endGroup();
}
}
//-----------------------------------------------------------------------------
FunctionSheet::~FunctionSheet() {
if (m_isFloating) {
TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath(mySettingsFileName));
QSettings mySettings(toQString(fp), QSettings::IniFormat);
mySettings.beginGroup("Dialogs");
mySettings.setValue("FunctionSpreadsheet", geometry());
mySettings.endGroup();
}
}
//-----------------------------------------------------------------------------
bool FunctionSheet::anyWidgetHasFocus() {
return hasFocus() || m_rowViewer->hasFocus() ||
m_columnHeadViewer->hasFocus() || m_cellViewer->hasFocus();
}
//-----------------------------------------------------------------------------
void FunctionSheet::setSelection(FunctionSelection *selection) {
m_selection = selection;
m_selection->setColumnToCurveMapper(
new FunctionSheetColumnToCurveMapper(this));
}
//-----------------------------------------------------------------------------
void FunctionSheet::showEvent(QShowEvent *e) {
m_frameScroller.registerFrameScroller();
SpreadsheetViewer::showEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheet::hideEvent(QHideEvent *e) {
m_frameScroller.unregisterFrameScroller();
SpreadsheetViewer::hideEvent(e);
}
//-----------------------------------------------------------------------------
void FunctionSheet::onFrameSwitched() {
setCurrentRow(getCurrentFrame());
m_rowViewer->update();
m_cellViewer->update();
}
//-----------------------------------------------------------------------------
void FunctionSheet::setCurrentFrame(int frame) {
if (getFrameHandle()) getFrameHandle()->setFrame(frame);
}
//-----------------------------------------------------------------------------
int FunctionSheet::getCurrentFrame() const {
return getFrameHandle() ? getFrameHandle()->getFrame() : 0;
}
//-----------------------------------------------------------------------------
int FunctionSheet::getChannelCount() {
if (m_functionTreeModel == 0)
return 0;
else
return m_functionTreeModel->getActiveChannelCount();
}
//-----------------------------------------------------------------------------
FunctionTreeModel::Channel *FunctionSheet::getChannel(int column) {
if (m_functionTreeModel == 0)
return 0;
else
return m_functionTreeModel->getActiveChannel(column);
}
//-----------------------------------------------------------------------------
TDoubleParam *FunctionSheet::getCurve(int column) {
FunctionTreeModel::Channel *channel = getChannel(column);
return channel ? channel->getParam() : 0;
}
//-----------------------------------------------------------------------------
void FunctionSheet::setModel(FunctionTreeModel *model) {
m_functionTreeModel = model;
}
//-----------------------------------------------------------------------------
void FunctionSheet::setViewer(FunctionViewer *viewer) {
m_functionViewer = viewer;
}
//-----------------------------------------------------------------------------
QRect FunctionSheet::getSelectedCells() const {
if (getSelection())
return getSelection()->getSelectedCells();
else
return QRect();
}
//-----------------------------------------------------------------------------
void FunctionSheet::selectCells(const QRect &selectedCells) {
m_selectedCells = selectedCells;
if (getSelection()) {
QList<TDoubleParam *> curves;
for (int c = selectedCells.left(); c <= selectedCells.right(); c++) {
TDoubleParam *param = 0;
if (c < getChannelCount()) param = getChannel(c)->getParam();
curves.push_back(param);
}
getSelection()->selectCells(selectedCells, curves);
if (selectedCells.width() == 1 && curves[0] &&
!getChannel(selectedCells.x())->isCurrent())
getChannel(selectedCells.x())->setIsCurrent(true);
}
updateAll();
}
//-----------------------------------------------------------------------------
void FunctionSheet::updateAll() {
m_rowViewer->update();
m_columnHeadViewer->update();
m_cellViewer->update();
setColumnCount(getChannelCount());
}
//-----------------------------------------------------------------------------
/*! Display expression name of the current segment
*/
QString FunctionSheet::getSelectedParamName() {
if (m_functionTreeModel->getCurrentChannel())
return m_functionTreeModel->getCurrentChannel()->getExprRefName();
else
return QString();
}
//-----------------------------------------------------------------------------
int FunctionSheet::getColumnIndexByCurve(TDoubleParam *param) const {
return m_functionTreeModel->getColumnIndexByCurve(param);
}
//-----------------------------------------------------------------------------
/*! scroll column to show the current one
*/
void FunctionSheet::onCurrentChannelChanged(
FunctionTreeModel::Channel *channel) {
if (!channel) return;
for (int c = 0; c < getChannelCount(); c++) {
FunctionTreeModel::Channel *tmpChan = getChannel(c);
if (tmpChan == channel) {
ensureVisibleCol(c);
return;
}
}
}