diff --git a/data/themes/classic/edit_tangent.png b/data/themes/classic/edit_tangent.png new file mode 100644 index 00000000000..438673b33a9 Binary files /dev/null and b/data/themes/classic/edit_tangent.png differ diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index ac22a14ec21..8667d12ce22 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -22,6 +22,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(255, 119, 175, 150); qproperty-nodeOutValueColor: rgba(129, 231, 181, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgb( 255, 51, 51 ); /* Grid colors */ qproperty-lineColor: rgba(128, 128, 128, 80); diff --git a/data/themes/default/edit_tangent.png b/data/themes/default/edit_tangent.png new file mode 100644 index 00000000000..7bc4000947d Binary files /dev/null and b/data/themes/default/edit_tangent.png differ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 092332eee65..78e2843aca1 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -57,6 +57,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(103, 73, 194, 150); qproperty-nodeOutValueColor: rgba(125, 40, 40, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgba(215, 210, 254, 150); /* Grid colors */ qproperty-lineColor: #292929; diff --git a/include/AutomationClip.h b/include/AutomationClip.h index ceb5611c958..0b49978c7f7 100644 --- a/include/AutomationClip.h +++ b/include/AutomationClip.h @@ -46,6 +46,7 @@ class TimePos; namespace gui { class AutomationClipView; +class AutomationEditor; } // namespace gui @@ -111,6 +112,13 @@ class LMMS_EXPORT AutomationClip : public Clip void resetNodes(const int tick0, const int tick1); + /** + * @brief Resets the tangents from the nodes between the given ticks + * @param Int first tick of the range + * @param Int second tick of the range + */ + void resetTangents(const int tick0, const int tick1); + void recordValue(TimePos time, float value); TimePos setDragValue( const TimePos & time, @@ -151,6 +159,17 @@ class LMMS_EXPORT AutomationClip : public Clip return m_timeMap.isEmpty() == false; } + static bool supportsTangentEditing(ProgressionType pType) + { + // Update function if we have new progression types that support tangent editing + return pType == ProgressionType::CubicHermite; + } + + inline bool canEditTangents() const + { + return supportsTangentEditing(m_progressionType); + } + float valueAt( const TimePos & _time ) const; float *valuesAfter( const TimePos & _time ) const; @@ -219,6 +238,9 @@ public slots: bool m_dragging; bool m_dragKeepOutValue; // Should we keep the current dragged node's outValue? float m_dragOutValue; // The outValue of the dragged node's + bool m_dragLockedTan; // If the dragged node has it's tangents locked + float m_dragInTan; // The dragged node's inTangent + float m_dragOutTan; // The dragged node's outTangent bool m_isRecording; float m_lastRecordedValue; @@ -230,6 +252,7 @@ public slots: friend class gui::AutomationClipView; friend class AutomationNode; + friend class gui::AutomationEditor; } ; @@ -261,6 +284,11 @@ inline float OUTTAN(AutomationClip::TimemapIterator it) return it->getOutTangent(); } +inline float LOCKEDTAN(AutomationClip::TimemapIterator it) +{ + return it->lockedTangents(); +} + inline int POS(AutomationClip::TimemapIterator it) { return it.key(); diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index ecefa8b26f1..dad0e4916f4 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -63,6 +63,7 @@ class AutomationEditor : public QWidget, public JournallingObject Q_PROPERTY(QColor lineColor MEMBER m_lineColor) Q_PROPERTY(QColor nodeInValueColor MEMBER m_nodeInValueColor) Q_PROPERTY(QColor nodeOutValueColor MEMBER m_nodeOutValueColor) + Q_PROPERTY(QColor nodeTangentLineColor MEMBER m_nodeTangentLineColor) Q_PROPERTY(QBrush scaleColor MEMBER m_scaleColor) Q_PROPERTY(QBrush graphColor MEMBER m_graphColor) Q_PROPERTY(QColor crossColor MEMBER m_crossColor) @@ -91,7 +92,8 @@ class AutomationEditor : public QWidget, public JournallingObject { Draw, Erase, - DrawOutValues + DrawOutValues, + EditTangents }; public slots: @@ -118,6 +120,13 @@ public slots: inline void drawLevelTick(QPainter & p, int tick, float value); timeMap::iterator getNodeAt(int x, int y, bool outValue = false, int r = 5); + /** + * @brief Given a mouse X coordinate, returns a timeMap::iterator that points to + * the closest node. + * @param Int X coordinate + * @return timeMap::iterator with the closest node or timeMap.end() if there are no nodes. + */ + timeMap::iterator getClosestNode(int x); void drawLine( int x0, float y0, int x1, float y1 ); bool fineTuneValue(timeMap::iterator node, bool editingOutValue); @@ -133,6 +142,12 @@ protected slots: void setEditMode(int mode); void setProgressionType(AutomationClip::ProgressionType type); + /** + * @brief This method handles the AutomationEditorWindow event of changing + * progression types. After that, it calls updateEditTanButton so the edit + * tangents button is updated accordingly + * @param Int New progression type + */ void setProgressionType(int type); void setTension(); @@ -153,7 +168,9 @@ protected slots: EraseValues, MoveOutValue, ResetOutValues, - DrawLine + DrawLine, + MoveTangent, + ResetTangents } ; // some constants... @@ -173,6 +190,7 @@ protected slots: static QPixmap * s_toolDraw; static QPixmap * s_toolErase; static QPixmap * s_toolDrawOut; + static QPixmap * s_toolEditTangents; static QPixmap * s_toolMove; static QPixmap * s_toolYFlip; static QPixmap * s_toolXFlip; @@ -215,6 +233,11 @@ protected slots: // Time position (key) of automation node whose outValue is being dragged int m_draggedOutValueKey; + // The tick from the node whose tangent is being dragged + int m_draggedTangentTick; + // Whether the tangent being dragged is the InTangent or OutTangent + bool m_draggedOutTangent; + EditMode m_editMode; bool m_mouseDownLeft; @@ -225,6 +248,7 @@ protected slots: void drawCross(QPainter & p ); void drawAutomationPoint( QPainter & p, timeMap::iterator it ); + void drawAutomationTangents(QPainter& p, timeMap::iterator it); bool inPatternEditor(); QColor m_barLineColor; @@ -233,6 +257,7 @@ protected slots: QBrush m_graphColor; QColor m_nodeInValueColor; QColor m_nodeOutValueColor; + QColor m_nodeTangentLineColor; QBrush m_scaleColor; QColor m_crossColor; QColor m_backgroundShade; @@ -285,8 +310,21 @@ protected slots: private slots: void updateWindowTitle(); + void setProgressionType(int progType); + /** + * @brief The Edit Tangent edit mode should only be available for + * Cubic Hermite progressions, so this method is responsable for disabling it + * for other edit modes and reenabling it when it changes back to the Edit Tangent + * mode. + */ + void updateEditTanButton(); private: + QAction* m_drawAction; + QAction* m_eraseAction; + QAction* m_drawOutAction; + QAction* m_editTanAction; + QAction* m_discreteAction; QAction* m_linearAction; QAction* m_cubicHermiteAction; diff --git a/include/AutomationNode.h b/include/AutomationNode.h index a922109e604..60154332f2c 100644 --- a/include/AutomationNode.h +++ b/include/AutomationNode.h @@ -125,6 +125,22 @@ class AutomationNode m_outTangent = tangent; } + /** + * @brief Checks if the tangents from the node are locked + */ + inline const bool lockedTangents() const + { + return m_lockedTangents; + } + + /** + * @brief Locks or Unlocks the tangents from this node + */ + inline void setLockedTangents(bool b) + { + m_lockedTangents = b; + } + /** * @brief Sets the clip this node belongs to * @param AutomationClip* clip that m_clip will be @@ -152,6 +168,11 @@ class AutomationNode // outValue are equal, inTangent and outTangent are equal too. float m_inTangent; float m_outTangent; + + // If the tangents were edited manually, this will be true. That way + // the tangents from this node will not be recalculated. It's set back + // to false if the tangents are reset. + bool m_lockedTangents; }; } // namespace lmms diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index 3b36f6b49b7..3bfc5cf8edd 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -422,6 +422,32 @@ void AutomationClip::resetNodes(const int tick0, const int tick1) +void AutomationClip::resetTangents(const int tick0, const int tick1) +{ + if (tick0 == tick1) + { + auto it = m_timeMap.find(TimePos(tick0)); + if (it != m_timeMap.end()) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } + return; + } + + TimePos start = TimePos(std::min(tick0, tick1)); + TimePos end = TimePos(std::max(tick0, tick1)); + + for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } +} + + + + void AutomationClip::recordValue(TimePos time, float value) { QMutexLocker m(&m_clipMutex); @@ -467,16 +493,31 @@ TimePos AutomationClip::setDragValue( // inValue m_dragKeepOutValue = false; + // We will set the tangents back to what they were if the node had + // its tangents locked + m_dragLockedTan = false; + // Check if we already have a node on the position we are dragging // and if we do, store the outValue so the discrete jump can be kept + // and information about the tangents timeMap::iterator it = m_timeMap.find(newTime); if (it != m_timeMap.end()) { + // If we don't have a discrete jump, the outValue will be the + // same as the inValue if (OFFSET(it) != 0) { m_dragKeepOutValue = true; m_dragOutValue = OUTVAL(it); } + // For the tangents, we will only keep them if the tangents were + // locked + if (LOCKEDTAN(it)) + { + m_dragLockedTan = true; + m_dragInTan = INTAN(it); + m_dragOutTan = OUTTAN(it); + } } this->removeNode(newTime); @@ -489,12 +530,31 @@ TimePos AutomationClip::setDragValue( generateTangents(); + TimePos returnedPos; + if (m_dragKeepOutValue) { - return this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + returnedPos = this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + } + else + { + returnedPos = this->putValue(time, value, quantPos, controlKey); + } + + // Set the tangents on the newly created node if they were locked + // before dragging + if (m_dragLockedTan) + { + timeMap::iterator it = m_timeMap.find(returnedPos); + if (it != m_timeMap.end()) + { + it.value().setInTangent(m_dragInTan); + it.value().setOutTangent(m_dragOutTan); + it.value().setLockedTangents(true); + } } - return this->putValue(time, value, quantPos, controlKey); + return returnedPos; } @@ -783,6 +843,9 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) element.setAttribute("pos", POS(it)); element.setAttribute("value", INVAL(it)); element.setAttribute("outValue", OUTVAL(it)); + element.setAttribute("inTan", INTAN(it)); + element.setAttribute("outTan", OUTTAN(it)); + element.setAttribute("lockedTan", static_cast(LOCKEDTAN(it))); _this.appendChild( element ); } @@ -804,6 +867,11 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { QMutexLocker m(&m_clipMutex); + // Legacy compatibility: Previously tangents were not stored in + // the project file. So if any node doesn't have tangent information + // we will generate the tangents + bool shouldGenerateTangents = false; + clear(); movePosition( _this.attribute( "pos" ).toInt() ); @@ -828,6 +896,22 @@ void AutomationClip::loadSettings( const QDomElement & _this ) float timeMapOutValue = LocaleHelper::toFloat(element.attribute("outValue")); m_timeMap[timeMapPos] = AutomationNode(this, timeMapInValue, timeMapOutValue, timeMapPos); + + // Load tangents if there is information about it (it's enough to check for either inTan or outTan) + if (element.hasAttribute("inTan")) + { + float inTan = LocaleHelper::toFloat(element.attribute("inTan")); + float outTan = LocaleHelper::toFloat(element.attribute("outTan")); + bool lockedTan = static_cast(element.attribute("lockedTan", "0").toInt()); + + m_timeMap[timeMapPos].setInTangent(inTan); + m_timeMap[timeMapPos].setOutTangent(outTan); + m_timeMap[timeMapPos].setLockedTangents(lockedTan); + } + else + { + shouldGenerateTangents = true; + } } else if( element.tagName() == "object" ) { @@ -851,7 +935,8 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { changeLength( len ); } - generateTangents(); + + if (shouldGenerateTangents) { generateTangents(); } } @@ -1108,6 +1193,12 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it) { + // Skip the node if it has locked tangents (were manually edited) + if (LOCKEDTAN(it)) + { + continue; + } + if (it + 1 == m_timeMap.end()) { // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents diff --git a/src/core/AutomationNode.cpp b/src/core/AutomationNode.cpp index eee4df8d21e..f15c28f8028 100644 --- a/src/core/AutomationNode.cpp +++ b/src/core/AutomationNode.cpp @@ -37,7 +37,8 @@ AutomationNode::AutomationNode() : m_inValue(0), m_outValue(0), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -47,7 +48,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float value, int pos) : m_inValue(value), m_outValue(value), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -57,7 +59,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float inValue, float outVal m_inValue(inValue), m_outValue(outValue), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index f98066bbaa2..282c335dfe1 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -68,6 +68,7 @@ namespace lmms::gui QPixmap * AutomationEditor::s_toolDraw = nullptr; QPixmap * AutomationEditor::s_toolErase = nullptr; QPixmap * AutomationEditor::s_toolDrawOut = nullptr; +QPixmap * AutomationEditor::s_toolEditTangents = nullptr; QPixmap * AutomationEditor::s_toolMove = nullptr; QPixmap * AutomationEditor::s_toolYFlip = nullptr; QPixmap * AutomationEditor::s_toolXFlip = nullptr; @@ -106,6 +107,7 @@ AutomationEditor::AutomationEditor() : m_graphColor(Qt::SolidPattern), m_nodeInValueColor(0, 0, 0), m_nodeOutValueColor(0, 0, 0), + m_nodeTangentLineColor(0, 0, 0), m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), m_backgroundShade(0, 0, 0) @@ -180,6 +182,10 @@ AutomationEditor::AutomationEditor() : { s_toolDrawOut = new QPixmap(embed::getIconPixmap("edit_draw_outvalue")); } + if (s_toolEditTangents == nullptr) + { + s_toolEditTangents = new QPixmap(embed::getIconPixmap("edit_tangent")); + } if (s_toolMove == nullptr) { s_toolMove = new QPixmap(embed::getIconPixmap("edit_move")); @@ -470,6 +476,17 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) Engine::getSong()->setModified(); } }; + auto resetTangent = [this](timeMap::iterator node) + { + if (node != m_clip->getTimeMap().end()) + { + // Unlock the tangents from that node + node.value().setLockedTangents(false); + // Recalculate the tangents + m_clip->generateTangents(node, 1); + Engine::getSong()->setModified(); + } + }; // If we clicked inside the AutomationEditor viewport (where the nodes are represented) if (mouseEvent->y() > TOP_MARGIN && mouseEvent->x() >= VALUES_WIDTH) @@ -654,6 +671,47 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) } break; } + case EditMode::EditTangents: + { + if (!m_clip->canEditTangents()) + { + update(); + return; + } + + m_clip->addJournalCheckPoint(); + + // Gets the closest node to the mouse click + timeMap::iterator node = getClosestNode(mouseEvent->x()); + + // Starts dragging a tangent + if (m_mouseDownLeft && node != tm.end()) + { + // Lock the tangents from that node, so it can only be + // manually edited + node.value().setLockedTangents(true); + + m_draggedTangentTick = POS(node); + + // Are we dragging the out or in tangent? + m_draggedOutTangent = posTicks >= m_draggedTangentTick; + + m_action = Action::MoveTangent; + } + // Resets node's tangent + else if (m_mouseDownRight) + { + // Resets tangent from node + resetTangent(node); + + // Update the last clicked position so we reset all tangents from + // that point up to the point we release the mouse button + m_drawLastTick = posTicks; + + m_action = Action::ResetTangents; + } + break; + } } update(); @@ -862,6 +920,51 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } break; } + case EditMode::EditTangents: + { + // If we moved the mouse past the beginning correct the position in ticks + posTicks = std::max(posTicks, 0); + + if (m_mouseDownLeft && m_action == Action::MoveTangent) + { + timeMap& tm = m_clip->getTimeMap(); + auto it = tm.find(m_draggedTangentTick); + + // Safety check + if (it == tm.end()) + { + update(); + return; + } + + // Calculate new tangent + float y = m_draggedOutTangent + ? yCoordOfLevel(OUTVAL(it)) + : yCoordOfLevel(INVAL(it)); + float dy = m_draggedOutTangent + ? y - mouseEvent->y() + : mouseEvent->y() - y; + float dx = std::abs(posTicks - POS(it)); + float newTangent = dy / std::max(dx, 1.0f); + + if (m_draggedOutTangent) + { + it.value().setOutTangent(newTangent); + } + else + { + it.value().setInTangent(newTangent); + } + } + else if (m_mouseDownRight && m_action == Action::ResetTangents) + { + // Resets all tangents from the last clicked tick up to the current position tick + m_clip->resetTangents(m_drawLastTick, posTicks); + + Engine::getSong()->setModified(); + } + break; + } } } else // If the mouse Y position is above the AutomationEditor viewport @@ -937,6 +1040,43 @@ inline void AutomationEditor::drawAutomationPoint(QPainter & p, timeMap::iterato +inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::iterator it) +{ + int x = xCoordOfTick(POS(it)); + int y, tx, ty; + + // The tangent value correlates the variation in the node value related to the increase + // in ticks. So to have a proportionate drawing of the tangent line, we need to find the + // relation between the number of pixels per tick and the number of pixels per value level. + float viewportHeight = (height() - SCROLLBAR_SIZE - 1) - TOP_MARGIN; + float pixelsPerTick = m_ppb / TimePos::ticksPerBar(); + // std::abs just in case the topLevel is smaller than the bottomLevel for some reason + float pixelsPerLevel = std::abs(viewportHeight / (m_topLevel - m_bottomLevel)); + float proportion = pixelsPerLevel / pixelsPerTick; + + p.setPen(QPen(m_nodeTangentLineColor)); + p.setBrush(QBrush(m_nodeTangentLineColor)); + + y = yCoordOfLevel(INVAL(it)); + tx = x - 20; + ty = y + 20 * INTAN(it) * proportion; + p.drawLine(x, y, tx, ty); + p.setBrush(QBrush(m_nodeTangentLineColor.darker(200))); + p.drawEllipse(tx - 3, ty - 3, 6, 6); + + p.setBrush(QBrush(m_nodeTangentLineColor)); + + y = yCoordOfLevel(OUTVAL(it)); + tx = x + 20; + ty = y - 20 * OUTTAN(it) * proportion; + p.drawLine(x, y, tx, ty); + p.setBrush(QBrush(m_nodeTangentLineColor.darker(200))); + p.drawEllipse(tx - 3, ty - 3, 6, 6); +} + + + + void AutomationEditor::paintEvent(QPaintEvent * pe ) { QStyleOption opt; @@ -1197,6 +1337,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // Draw circle drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } ++it; } @@ -1213,6 +1358,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } // Draw circle(the last one) drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } } } else @@ -1267,6 +1417,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) else { cursor = s_toolDrawOut; } break; } + case EditMode::EditTangents: + { + cursor = m_action == Action::MoveTangent ? s_toolMove : s_toolEditTangents; + break; + } } QPoint mousePosition = mapFromGlobal( QCursor::pos() ); if (cursor != nullptr && mousePosition.y() > TOP_MARGIN + SCROLLBAR_SIZE) @@ -1819,6 +1974,49 @@ AutomationEditor::timeMap::iterator AutomationEditor::getNodeAt(int x, int y, bo return tm.end(); } +AutomationEditor::timeMap::iterator AutomationEditor::getClosestNode(int x) +{ + // Remove the VALUES_WIDTH from the x position, so we have the actual viewport x + x -= VALUES_WIDTH; + // Convert the x position to the position in ticks + int posTicks = (x * TimePos::ticksPerBar() / m_ppb) + m_currentPosition; + + // Get our pattern timeMap and create a iterator so we can check the nodes + timeMap& tm = m_clip->getTimeMap(); + + if (tm.isEmpty()) { return tm.end(); } + + // Get the node with an equal or higher position + auto it = tm.lowerBound(posTicks); + + // If there are no nodes equal or higher than the position return + // the one before it + if (it == tm.end()) + { + --it; + return it; + } + // If the node returned is the first, return it + else if (it == tm.begin()) + { + return it; + } + // Else return the closest node + else + { + // Distance from node to the right + int distanceRight = std::abs(POS(it) - posTicks); + // Distance from node to the left + int distanceLeft = std::abs(POS(--it) - posTicks); + + if (distanceLeft >= distanceRight) + { + ++it; + } + return it; + } +} + @@ -1839,24 +2037,29 @@ AutomationEditorWindow::AutomationEditorWindow() : DropToolBar *editActionsToolBar = addDropToolBarToTop(tr("Edit actions")); auto editModeGroup = new ActionGroup(this); - QAction* drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); - drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); - drawAction->setChecked(true); + m_drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); + m_drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); + m_drawAction->setChecked(true); + + m_eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); + m_eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); - QAction* eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); - eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); + m_drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); + m_drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); - QAction* drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); - drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); + m_editTanAction = editModeGroup->addAction(embed::getIconPixmap("edit_tangent"), tr("Edit tangents mode (Shift+T)")); + m_editTanAction->setShortcut(Qt::SHIFT | Qt::Key_T); + m_editTanAction->setEnabled(false); m_flipYAction = new QAction(embed::getIconPixmap("flip_y"), tr("Flip vertically"), this); m_flipXAction = new QAction(embed::getIconPixmap("flip_x"), tr("Flip horizontally"), this); connect(editModeGroup, SIGNAL(triggered(int)), m_editor, SLOT(setEditMode(int))); - editActionsToolBar->addAction(drawAction); - editActionsToolBar->addAction(eraseAction); - editActionsToolBar->addAction(drawOutAction); + editActionsToolBar->addAction(m_drawAction); + editActionsToolBar->addAction(m_eraseAction); + editActionsToolBar->addAction(m_drawOutAction); + editActionsToolBar->addAction(m_editTanAction); editActionsToolBar->addAction(m_flipXAction); editActionsToolBar->addAction(m_flipYAction); @@ -1874,7 +2077,7 @@ AutomationEditorWindow::AutomationEditorWindow() : m_cubicHermiteAction = progression_type_group->addAction( embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression")); - connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int))); + connect(progression_type_group, SIGNAL(triggered(int)), this, SLOT(setProgressionType(int))); // setup tension-stuff m_tensionKnob = new Knob( KnobType::Small17, this, "Tension" ); @@ -2014,6 +2217,7 @@ void AutomationEditorWindow::setCurrentClip(AutomationClip* clip) connect(m_flipYAction, SIGNAL(triggered()), clip, SLOT(flipY())); } + updateEditTanButton(); emit currentClipChanged(); } @@ -2102,5 +2306,17 @@ void AutomationEditorWindow::updateWindowTitle() setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_clip->name() ) ); } +void AutomationEditorWindow::setProgressionType(int progType) +{ + m_editor->setProgressionType(progType); + updateEditTanButton(); +} + +void AutomationEditorWindow::updateEditTanButton() +{ + auto progType = currentClip()->progressionType(); + m_editTanAction->setEnabled(AutomationClip::supportsTangentEditing(progType)); + if (!m_editTanAction->isEnabled() && m_editTanAction->isChecked()) { m_drawAction->trigger(); } +} } // namespace lmms::gui