From 986fb68e7f05bad52a99c7b1ddaf9d2c8aa65367 Mon Sep 17 00:00:00 2001 From: M374LX Date: Wed, 25 Nov 2020 19:22:45 -0300 Subject: [PATCH 1/6] Split TrackContentObject off of Track --- include/Track.h | 145 +---------------------- include/TrackContentObject.h | 201 +++++++++++++++++++++++++++++++ src/core/CMakeLists.txt | 1 + src/core/Track.cpp | 178 ---------------------------- src/core/TrackContentObject.cpp | 204 ++++++++++++++++++++++++++++++++ 5 files changed, 407 insertions(+), 322 deletions(-) create mode 100644 include/TrackContentObject.h create mode 100644 src/core/TrackContentObject.cpp diff --git a/include/Track.h b/include/Track.h index dd6066986a2..e5bf9d4ed58 100644 --- a/include/Track.h +++ b/include/Track.h @@ -41,6 +41,7 @@ #include "ModelView.h" #include "DataFile.h" #include "FadeButton.h" +#include "TrackContentObject.h" class QMenu; @@ -76,150 +77,6 @@ const int TCO_BORDER_WIDTH = 2; char const *const FILENAME_FILTER = "[\\0000-\x1f\"*/:<>?\\\\|\x7f]"; -class LMMS_EXPORT TrackContentObject : public Model, public JournallingObject -{ - Q_OBJECT - MM_OPERATORS - mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); - mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); -public: - TrackContentObject( Track * track ); - virtual ~TrackContentObject(); - - inline Track * getTrack() const - { - return m_track; - } - - inline const QString & name() const - { - return m_name; - } - - inline void setName( const QString & name ) - { - m_name = name; - emit dataChanged(); - } - - QString displayName() const override - { - return name(); - } - - - inline const MidiTime & startPosition() const - { - return m_startPosition; - } - - inline MidiTime endPosition() const - { - const int sp = m_startPosition; - return sp + m_length; - } - - inline const MidiTime & length() const - { - return m_length; - } - - inline void setAutoResize( const bool r ) - { - m_autoResize = r; - } - - inline const bool getAutoResize() const - { - return m_autoResize; - } - - QColor color() const - { - return m_color; - } - - void setColor( const QColor & c ) - { - m_color = c; - } - - bool hasColor(); - - void useCustomClipColor( bool b ); - - bool usesCustomClipColor() - { - return m_useCustomClipColor; - } - - virtual void movePosition( const MidiTime & pos ); - virtual void changeLength( const MidiTime & length ); - - virtual TrackContentObjectView * createView( TrackView * tv ) = 0; - - inline void selectViewOnCreate( bool select ) - { - m_selectViewOnCreate = select; - } - - inline bool getSelectViewOnCreate() - { - return m_selectViewOnCreate; - } - - /// Returns true if and only if a->startPosition() < b->startPosition() - static bool comparePosition(const TrackContentObject* a, const TrackContentObject* b); - - MidiTime startTimeOffset() const; - void setStartTimeOffset( const MidiTime &startTimeOffset ); - - void updateColor(); - - // Will copy the state of a TCO to another TCO - static void copyStateTo( TrackContentObject *src, TrackContentObject *dst ); - -public slots: - void toggleMute(); - - -signals: - void lengthChanged(); - void positionChanged(); - void destroyedTCO(); - void trackColorChanged(); - - -private: - enum Actions - { - NoAction, - Move, - Resize - } ; - - Track * m_track; - QString m_name; - - MidiTime m_startPosition; - MidiTime m_length; - MidiTime m_startTimeOffset; - - BoolModel m_mutedModel; - BoolModel m_soloModel; - bool m_autoResize; - - bool m_selectViewOnCreate; - - QColor m_color; - bool m_useCustomClipColor; - - friend class TrackContentObjectView; - -} ; - - - class TrackContentObjectView : public selectableObject, public ModelView { Q_OBJECT diff --git a/include/TrackContentObject.h b/include/TrackContentObject.h new file mode 100644 index 00000000000..e73c49ba79a --- /dev/null +++ b/include/TrackContentObject.h @@ -0,0 +1,201 @@ +/* + * TrackConteintObject.h - declaration of TrackContentObject class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef TRACK_CONTENT_OBJECT_H +#define TRACK_CONTENT_OBJECT_H + +#include +#include +#include +#include +#include +#include + +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Rubberband.h" +#include "JournallingObject.h" +#include "AutomatableModel.h" +#include "ModelView.h" +#include "DataFile.h" +#include "FadeButton.h" + + +class QMenu; +class QPushButton; + +class PixmapButton; +class TextFloat; +class Track; +class TrackContentObjectView; +class TrackContainer; +class TrackContainerView; +class TrackContentWidget; +class TrackView; + + +class LMMS_EXPORT TrackContentObject : public Model, public JournallingObject +{ + Q_OBJECT + MM_OPERATORS + mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); + mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); +public: + TrackContentObject( Track * track ); + virtual ~TrackContentObject(); + + inline Track * getTrack() const + { + return m_track; + } + + inline const QString & name() const + { + return m_name; + } + + inline void setName( const QString & name ) + { + m_name = name; + emit dataChanged(); + } + + QString displayName() const override + { + return name(); + } + + + inline const MidiTime & startPosition() const + { + return m_startPosition; + } + + inline MidiTime endPosition() const + { + const int sp = m_startPosition; + return sp + m_length; + } + + inline const MidiTime & length() const + { + return m_length; + } + + inline void setAutoResize( const bool r ) + { + m_autoResize = r; + } + + inline const bool getAutoResize() const + { + return m_autoResize; + } + + QColor color() const + { + return m_color; + } + + void setColor( const QColor & c ) + { + m_color = c; + } + + bool hasColor(); + + void useCustomClipColor( bool b ); + + bool usesCustomClipColor() + { + return m_useCustomClipColor; + } + + virtual void movePosition( const MidiTime & pos ); + virtual void changeLength( const MidiTime & length ); + + virtual TrackContentObjectView * createView( TrackView * tv ) = 0; + + inline void selectViewOnCreate( bool select ) + { + m_selectViewOnCreate = select; + } + + inline bool getSelectViewOnCreate() + { + return m_selectViewOnCreate; + } + + /// Returns true if and only if a->startPosition() < b->startPosition() + static bool comparePosition(const TrackContentObject* a, const TrackContentObject* b); + + MidiTime startTimeOffset() const; + void setStartTimeOffset( const MidiTime &startTimeOffset ); + + void updateColor(); + + // Will copy the state of a TCO to another TCO + static void copyStateTo( TrackContentObject *src, TrackContentObject *dst ); + +public slots: + void toggleMute(); + + +signals: + void lengthChanged(); + void positionChanged(); + void destroyedTCO(); + void trackColorChanged(); + + +private: + enum Actions + { + NoAction, + Move, + Resize + } ; + + Track * m_track; + QString m_name; + + MidiTime m_startPosition; + MidiTime m_length; + MidiTime m_startTimeOffset; + + BoolModel m_mutedModel; + BoolModel m_soloModel; + bool m_autoResize; + + bool m_selectViewOnCreate; + + QColor m_color; + bool m_useCustomClipColor; + + friend class TrackContentObjectView; + +} ; + + +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 22bb1195103..61e8be3e44f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -70,6 +70,7 @@ set(LMMS_SRCS core/ToolPlugin.cpp core/Track.cpp core/TrackContainer.cpp + core/TrackContentObject.cpp core/ValueBuffer.cpp core/VstSyncController.cpp core/StepRecorder.cpp diff --git a/src/core/Track.cpp b/src/core/Track.cpp index c63e863bf9c..6ed7e5473cd 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -92,184 +92,6 @@ const int BARS_PER_GROUP = 4; TextFloat * TrackContentObjectView::s_textFloat = NULL; -// =========================================================================== -// TrackContentObject -// =========================================================================== -/*! \brief Create a new TrackContentObject - * - * Creates a new track content object for the given track. - * - * \param _track The track that will contain the new object - */ -TrackContentObject::TrackContentObject( Track * track ) : - Model( track ), - m_track( track ), - m_startPosition(), - m_length(), - m_mutedModel( false, this, tr( "Mute" ) ), - m_selectViewOnCreate( false ), - m_color( 128, 128, 128 ), - m_useCustomClipColor( false ) -{ - if( getTrack() ) - { - getTrack()->addTCO( this ); - } - setJournalling( false ); - movePosition( 0 ); - changeLength( 0 ); - setJournalling( true ); -} - - - - -/*! \brief Destroy a TrackContentObject - * - * Destroys the given track content object. - * - */ -TrackContentObject::~TrackContentObject() -{ - emit destroyedTCO(); - - if( getTrack() ) - { - getTrack()->removeTCO( this ); - } -} - - - - -/*! \brief Move this TrackContentObject's position in time - * - * If the track content object has moved, update its position. We - * also add a journal entry for undo and update the display. - * - * \param _pos The new position of the track content object. - */ -void TrackContentObject::movePosition( const MidiTime & pos ) -{ - MidiTime newPos = qMax(0, pos.getTicks()); - if (m_startPosition != newPos) - { - Engine::mixer()->requestChangeInModel(); - m_startPosition = newPos; - Engine::mixer()->doneChangeInModel(); - Engine::getSong()->updateLength(); - emit positionChanged(); - } -} - - - - -/*! \brief Change the length of this TrackContentObject - * - * If the track content object's length has changed, update it. We - * also add a journal entry for undo and update the display. - * - * \param _length The new length of the track content object. - */ -void TrackContentObject::changeLength( const MidiTime & length ) -{ - m_length = length; - Engine::getSong()->updateLength(); - emit lengthChanged(); -} - - - - -bool TrackContentObject::comparePosition(const TrackContentObject *a, const TrackContentObject *b) -{ - return a->startPosition() < b->startPosition(); -} - - - - -/*! \brief Copies the state of a TrackContentObject to another TrackContentObject - * - * This method copies the state of a TCO to another TCO - */ -void TrackContentObject::copyStateTo( TrackContentObject *src, TrackContentObject *dst ) -{ - // If the node names match we copy the state - if( src->nodeName() == dst->nodeName() ){ - QDomDocument doc; - QDomElement parent = doc.createElement( "StateCopy" ); - src->saveState( doc, parent ); - - const MidiTime pos = dst->startPosition(); - dst->restoreState( parent.firstChild().toElement() ); - dst->movePosition( pos ); - - AutomationPattern::resolveAllIDs(); - GuiApplication::instance()->automationEditor()->m_editor->updateAfterPatternChange(); - } -} - - - - -/*! \brief Mutes this TrackContentObject - * - * Restore the previous state of this track content object. This will - * restore the position or the length of the track content object - * depending on what was changed. - * - * \param _je The journal entry to undo - */ -void TrackContentObject::toggleMute() -{ - m_mutedModel.setValue( !m_mutedModel.value() ); - emit dataChanged(); -} - - - - -MidiTime TrackContentObject::startTimeOffset() const -{ - return m_startTimeOffset; -} - - - - -void TrackContentObject::setStartTimeOffset( const MidiTime &startTimeOffset ) -{ - m_startTimeOffset = startTimeOffset; -} - -// Update TCO color if it follows the track color -void TrackContentObject::updateColor() -{ - if( ! m_useCustomClipColor ) - { - emit trackColorChanged(); - } -} - - -void TrackContentObject::useCustomClipColor( bool b ) -{ - m_useCustomClipColor = b; - updateColor(); -} - - -bool TrackContentObject::hasColor() -{ - return usesCustomClipColor() || getTrack()->useColor(); -} - - - - - // =========================================================================== // trackContentObjectView // =========================================================================== diff --git a/src/core/TrackContentObject.cpp b/src/core/TrackContentObject.cpp new file mode 100644 index 00000000000..151c1766d66 --- /dev/null +++ b/src/core/TrackContentObject.cpp @@ -0,0 +1,204 @@ +/* + * TrackContentObject.cpp - implementation of TrackContentObject class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "TrackContentObject.h" + +#include "AutomationEditor.h" +#include "AutomationPattern.h" +#include "Engine.h" +#include "GuiApplication.h" +#include "Song.h" + + +/*! \brief Create a new TrackContentObject + * + * Creates a new track content object for the given track. + * + * \param _track The track that will contain the new object + */ +TrackContentObject::TrackContentObject( Track * track ) : + Model( track ), + m_track( track ), + m_startPosition(), + m_length(), + m_mutedModel( false, this, tr( "Mute" ) ), + m_selectViewOnCreate( false ), + m_color( 128, 128, 128 ), + m_useCustomClipColor( false ) +{ + if( getTrack() ) + { + getTrack()->addTCO( this ); + } + setJournalling( false ); + movePosition( 0 ); + changeLength( 0 ); + setJournalling( true ); +} + + + + +/*! \brief Destroy a TrackContentObject + * + * Destroys the given track content object. + * + */ +TrackContentObject::~TrackContentObject() +{ + emit destroyedTCO(); + + if( getTrack() ) + { + getTrack()->removeTCO( this ); + } +} + + + + +/*! \brief Move this TrackContentObject's position in time + * + * If the track content object has moved, update its position. We + * also add a journal entry for undo and update the display. + * + * \param _pos The new position of the track content object. + */ +void TrackContentObject::movePosition( const MidiTime & pos ) +{ + MidiTime newPos = qMax(0, pos.getTicks()); + if (m_startPosition != newPos) + { + Engine::mixer()->requestChangeInModel(); + m_startPosition = newPos; + Engine::mixer()->doneChangeInModel(); + Engine::getSong()->updateLength(); + emit positionChanged(); + } +} + + + + +/*! \brief Change the length of this TrackContentObject + * + * If the track content object's length has changed, update it. We + * also add a journal entry for undo and update the display. + * + * \param _length The new length of the track content object. + */ +void TrackContentObject::changeLength( const MidiTime & length ) +{ + m_length = length; + Engine::getSong()->updateLength(); + emit lengthChanged(); +} + + + + +bool TrackContentObject::comparePosition(const TrackContentObject *a, const TrackContentObject *b) +{ + return a->startPosition() < b->startPosition(); +} + + + + +/*! \brief Copies the state of a TrackContentObject to another TrackContentObject + * + * This method copies the state of a TCO to another TCO + */ +void TrackContentObject::copyStateTo( TrackContentObject *src, TrackContentObject *dst ) +{ + // If the node names match we copy the state + if( src->nodeName() == dst->nodeName() ){ + QDomDocument doc; + QDomElement parent = doc.createElement( "StateCopy" ); + src->saveState( doc, parent ); + + const MidiTime pos = dst->startPosition(); + dst->restoreState( parent.firstChild().toElement() ); + dst->movePosition( pos ); + + AutomationPattern::resolveAllIDs(); + GuiApplication::instance()->automationEditor()->m_editor->updateAfterPatternChange(); + } +} + + + + +/*! \brief Mutes this TrackContentObject + * + * Restore the previous state of this track content object. This will + * restore the position or the length of the track content object + * depending on what was changed. + * + * \param _je The journal entry to undo + */ +void TrackContentObject::toggleMute() +{ + m_mutedModel.setValue( !m_mutedModel.value() ); + emit dataChanged(); +} + + + + +MidiTime TrackContentObject::startTimeOffset() const +{ + return m_startTimeOffset; +} + + + + +void TrackContentObject::setStartTimeOffset( const MidiTime &startTimeOffset ) +{ + m_startTimeOffset = startTimeOffset; +} + +// Update TCO color if it follows the track color +void TrackContentObject::updateColor() +{ + if( ! m_useCustomClipColor ) + { + emit trackColorChanged(); + } +} + + +void TrackContentObject::useCustomClipColor( bool b ) +{ + m_useCustomClipColor = b; + updateColor(); +} + + +bool TrackContentObject::hasColor() +{ + return usesCustomClipColor() || getTrack()->useColor(); +} + From 2fb8dd908bd547dbfa2db60e325bf1711b8933d7 Mon Sep 17 00:00:00 2001 From: M374LX Date: Wed, 25 Nov 2020 20:55:26 -0300 Subject: [PATCH 2/6] Split TrackView off of Track --- include/AutomationTrack.h | 1 + include/BBTrack.h | 1 + include/InstrumentTrack.h | 1 + include/SampleTrack.h | 1 + include/Track.h | 119 ------- include/TrackView.h | 154 +++++++++ src/core/Track.cpp | 418 ----------------------- src/gui/CMakeLists.txt | 1 + src/gui/TrackView.cpp | 473 +++++++++++++++++++++++++++ src/gui/widgets/FxLineLcdSpinBox.cpp | 1 + 10 files changed, 633 insertions(+), 537 deletions(-) create mode 100644 include/TrackView.h create mode 100644 src/gui/TrackView.cpp diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index c8a0009e966..1b5074c02e6 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -28,6 +28,7 @@ #define AUTOMATION_TRACK_H #include "Track.h" +#include "TrackView.h" class AutomationTrack : public Track diff --git a/include/BBTrack.h b/include/BBTrack.h index 5f9cfe408f0..bf4bc9b11d8 100644 --- a/include/BBTrack.h +++ b/include/BBTrack.h @@ -32,6 +32,7 @@ #include #include "Track.h" +#include "TrackView.h" class TrackLabelButton; class TrackContainer; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 944791552dd..5676248dab2 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -38,6 +38,7 @@ #include "Pitch.h" #include "Plugin.h" #include "Track.h" +#include "TrackView.h" diff --git a/include/SampleTrack.h b/include/SampleTrack.h index e944936b332..819434e0a4e 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -33,6 +33,7 @@ #include "FxMixer.h" #include "FxLineLcdSpinBox.h" #include "Track.h" +#include "TrackView.h" class EffectRackView; class Knob; diff --git a/include/Track.h b/include/Track.h index e5bf9d4ed58..9efc2e34ea0 100644 --- a/include/Track.h +++ b/include/Track.h @@ -54,7 +54,6 @@ class TrackContentObjectView; class TrackContainer; class TrackContainerView; class TrackContentWidget; -class TrackView; const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; @@ -586,122 +585,4 @@ public slots: - -class TrackView : public QWidget, public ModelView, public JournallingObject -{ - Q_OBJECT -public: - TrackView( Track * _track, TrackContainerView* tcv ); - virtual ~TrackView(); - - inline const Track * getTrack() const - { - return m_track; - } - - inline Track * getTrack() - { - return m_track; - } - - inline TrackContainerView* trackContainerView() - { - return m_trackContainerView; - } - - inline TrackOperationsWidget * getTrackOperationsWidget() - { - return &m_trackOperationsWidget; - } - - inline QWidget * getTrackSettingsWidget() - { - return &m_trackSettingsWidget; - } - - inline TrackContentWidget * getTrackContentWidget() - { - return &m_trackContentWidget; - } - - bool isMovingTrack() const - { - return m_action == MoveTrack; - } - - virtual void update(); - - // Create a menu for assigning/creating channels for this track - // Currently instrument track and sample track supports it - virtual QMenu * createFxMenu(QString title, QString newFxLabel); - - -public slots: - virtual bool close(); - - -protected: - void modelChanged() override; - - void saveSettings( QDomDocument& doc, QDomElement& element ) override - { - Q_UNUSED(doc) - Q_UNUSED(element) - } - - void loadSettings( const QDomElement& element ) override - { - Q_UNUSED(element) - } - - QString nodeName() const override - { - return "trackview"; - } - - - void dragEnterEvent( QDragEnterEvent * dee ) override; - void dropEvent( QDropEvent * de ) override; - void mousePressEvent( QMouseEvent * me ) override; - void mouseMoveEvent( QMouseEvent * me ) override; - void mouseReleaseEvent( QMouseEvent * me ) override; - void paintEvent( QPaintEvent * pe ) override; - void resizeEvent( QResizeEvent * re ) override; - - -private: - enum Actions - { - NoAction, - MoveTrack, - ResizeTrack - } ; - - Track * m_track; - TrackContainerView * m_trackContainerView; - - TrackOperationsWidget m_trackOperationsWidget; - QWidget m_trackSettingsWidget; - TrackContentWidget m_trackContentWidget; - - Actions m_action; - - virtual FadeButton * getActivityIndicator() - { - return nullptr; - } - - void setIndicatorMute(FadeButton* indicator, bool muted); - - friend class TrackLabelButton; - - -private slots: - void createTCOView( TrackContentObject * tco ); - void muteChanged(); - -} ; - - - #endif diff --git a/include/TrackView.h b/include/TrackView.h new file mode 100644 index 00000000000..16ebd10ac4d --- /dev/null +++ b/include/TrackView.h @@ -0,0 +1,154 @@ +/* + * TrackView.h - declaration of TrackView class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + + +#ifndef TRACK_VIEW_H +#define TRACK_VIEW_H + +#include + +#include "JournallingObject.h" +#include "ModelView.h" +#include "Track.h" + + +class TrackView : public QWidget, public ModelView, public JournallingObject +{ + Q_OBJECT +public: + TrackView( Track * _track, TrackContainerView* tcv ); + virtual ~TrackView(); + + inline const Track * getTrack() const + { + return m_track; + } + + inline Track * getTrack() + { + return m_track; + } + + inline TrackContainerView* trackContainerView() + { + return m_trackContainerView; + } + + inline TrackOperationsWidget * getTrackOperationsWidget() + { + return &m_trackOperationsWidget; + } + + inline QWidget * getTrackSettingsWidget() + { + return &m_trackSettingsWidget; + } + + inline TrackContentWidget * getTrackContentWidget() + { + return &m_trackContentWidget; + } + + bool isMovingTrack() const + { + return m_action == MoveTrack; + } + + virtual void update(); + + // Create a menu for assigning/creating channels for this track + // Currently instrument track and sample track supports it + virtual QMenu * createFxMenu(QString title, QString newFxLabel); + + +public slots: + virtual bool close(); + + +protected: + void modelChanged() override; + + void saveSettings( QDomDocument& doc, QDomElement& element ) override + { + Q_UNUSED(doc) + Q_UNUSED(element) + } + + void loadSettings( const QDomElement& element ) override + { + Q_UNUSED(element) + } + + QString nodeName() const override + { + return "trackview"; + } + + + void dragEnterEvent( QDragEnterEvent * dee ) override; + void dropEvent( QDropEvent * de ) override; + void mousePressEvent( QMouseEvent * me ) override; + void mouseMoveEvent( QMouseEvent * me ) override; + void mouseReleaseEvent( QMouseEvent * me ) override; + void paintEvent( QPaintEvent * pe ) override; + void resizeEvent( QResizeEvent * re ) override; + + +private: + enum Actions + { + NoAction, + MoveTrack, + ResizeTrack + } ; + + Track * m_track; + TrackContainerView * m_trackContainerView; + + TrackOperationsWidget m_trackOperationsWidget; + QWidget m_trackSettingsWidget; + TrackContentWidget m_trackContentWidget; + + Actions m_action; + + virtual FadeButton * getActivityIndicator() + { + return nullptr; + } + + void setIndicatorMute(FadeButton* indicator, bool muted); + + friend class TrackLabelButton; + + +private slots: + void createTCOView( TrackContentObject * tco ); + void muteChanged(); + +} ; + + + +#endif diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 6ed7e5473cd..8c42cb9d1f6 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -2891,421 +2891,3 @@ BoolModel *Track::getMutedModel() return &m_mutedModel; } - - - - - -// =========================================================================== -// trackView -// =========================================================================== - -/*! \brief Create a new track View. - * - * The track View is handles the actual display of the track, including - * displaying its various widgets and the track segments. - * - * \param track The track to display. - * \param tcv The track Container View for us to be displayed in. - * \todo Is my description of these properties correct? - */ -TrackView::TrackView( Track * track, TrackContainerView * tcv ) : - QWidget( tcv->contentWidget() ), /*!< The Track Container View's content widget. */ - ModelView( NULL, this ), /*!< The model view of this track */ - m_track( track ), /*!< The track we're displaying */ - m_trackContainerView( tcv ), /*!< The track Container View we're displayed in */ - m_trackOperationsWidget( this ), /*!< Our trackOperationsWidget */ - m_trackSettingsWidget( this ), /*!< Our trackSettingsWidget */ - m_trackContentWidget( this ), /*!< Our trackContentWidget */ - m_action( NoAction ) /*!< The action we're currently performing */ -{ - setAutoFillBackground( true ); - QPalette pal; - pal.setColor( backgroundRole(), QColor( 32, 36, 40 ) ); - setPalette( pal ); - - m_trackSettingsWidget.setAutoFillBackground( true ); - - QHBoxLayout * layout = new QHBoxLayout( this ); - layout->setMargin( 0 ); - layout->setSpacing( 0 ); - layout->addWidget( &m_trackOperationsWidget ); - layout->addWidget( &m_trackSettingsWidget ); - layout->addWidget( &m_trackContentWidget, 1 ); - setFixedHeight( m_track->getHeight() ); - - resizeEvent( NULL ); - - setAcceptDrops( true ); - setAttribute( Qt::WA_DeleteOnClose, true ); - - - connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); - connect( m_track, - SIGNAL( trackContentObjectAdded( TrackContentObject * ) ), - this, SLOT( createTCOView( TrackContentObject * ) ), - Qt::QueuedConnection ); - - connect( &m_track->m_mutedModel, SIGNAL( dataChanged() ), - &m_trackContentWidget, SLOT( update() ) ); - - connect(&m_track->m_mutedModel, SIGNAL(dataChanged()), - this, SLOT(muteChanged())); - - connect( &m_track->m_soloModel, SIGNAL( dataChanged() ), - m_track, SLOT( toggleSolo() ), Qt::DirectConnection ); - - connect( &m_trackOperationsWidget, SIGNAL( colorChanged( QColor & ) ), - m_track, SLOT( trackColorChanged( QColor & ) ) ); - - connect( &m_trackOperationsWidget, SIGNAL( colorReset() ), - m_track, SLOT( trackColorReset() ) ); - - // create views for already existing TCOs - for( Track::tcoVector::iterator it = - m_track->m_trackContentObjects.begin(); - it != m_track->m_trackContentObjects.end(); ++it ) - { - createTCOView( *it ); - } - - m_trackContainerView->addTrackView( this ); -} - - - - -/*! \brief Destroy this track View. - * - */ -TrackView::~TrackView() -{ -} - - - - -/*! \brief Resize this track View. - * - * \param re the Resize Event to handle. - */ -void TrackView::resizeEvent( QResizeEvent * re ) -{ - if( ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt() ) - { - m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH_COMPACT, height() - 1 ); - m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT, height() - 1 ); - } - else - { - m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH, height() - 1 ); - m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH, height() - 1 ); - } - m_trackContentWidget.setFixedHeight( height() ); -} - - - - -/*! \brief Update this track View and all its content objects. - * - */ -void TrackView::update() -{ - m_trackContentWidget.update(); - if( !m_trackContainerView->fixedTCOs() ) - { - m_trackContentWidget.changePosition(); - } - QWidget::update(); -} - - - - -/*! \brief Create a menu for assigning/creating channels for this track. - * - */ -QMenu * TrackView::createFxMenu(QString title, QString newFxLabel) -{ - Q_UNUSED(title) - Q_UNUSED(newFxLabel) - return NULL; -} - - - - -/*! \brief Close this track View. - * - */ -bool TrackView::close() -{ - m_trackContainerView->removeTrackView( this ); - return QWidget::close(); -} - - - - -/*! \brief Register that the model of this track View has changed. - * - */ -void TrackView::modelChanged() -{ - m_track = castModel(); - assert( m_track != NULL ); - connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); - m_trackOperationsWidget.m_muteBtn->setModel( &m_track->m_mutedModel ); - m_trackOperationsWidget.m_soloBtn->setModel( &m_track->m_soloModel ); - ModelView::modelChanged(); - setFixedHeight( m_track->getHeight() ); -} - - - - -/*! \brief Start a drag event on this track View. - * - * \param dee the DragEnterEvent to start. - */ -void TrackView::dragEnterEvent( QDragEnterEvent * dee ) -{ - StringPairDrag::processDragEnterEvent( dee, "track_" + - QString::number( m_track->type() ) ); -} - - - - -/*! \brief Accept a drop event on this track View. - * - * We only accept drop events that are of the same type as this track. - * If so, we decode the data from the drop event by just feeding it - * back into the engine as a state. - * - * \param de the DropEvent to handle. - */ -void TrackView::dropEvent( QDropEvent * de ) -{ - QString type = StringPairDrag::decodeKey( de ); - QString value = StringPairDrag::decodeValue( de ); - if( type == ( "track_" + QString::number( m_track->type() ) ) ) - { - // value contains our XML-data so simply create a - // DataFile which does the rest for us... - DataFile dataFile( value.toUtf8() ); - Engine::mixer()->requestChangeInModel(); - m_track->restoreState( dataFile.content().firstChild().toElement() ); - Engine::mixer()->doneChangeInModel(); - de->accept(); - } -} - - - - -/*! \brief Handle a mouse press event on this track View. - * - * If this track container supports rubber band selection, let the - * widget handle that and don't bother with any other handling. - * - * If the left mouse button is pressed, we handle two things. If - * SHIFT is pressed, then we resize vertically. Otherwise we start - * the process of moving this track to a new position. - * - * Otherwise we let the widget handle the mouse event as normal. - * - * \param me the MouseEvent to handle. - */ -void TrackView::mousePressEvent( QMouseEvent * me ) -{ - - // If previously dragged too small, restore on shift-leftclick - if( height() < DEFAULT_TRACK_HEIGHT && - me->modifiers() & Qt::ShiftModifier && - me->button() == Qt::LeftButton ) - { - setFixedHeight( DEFAULT_TRACK_HEIGHT ); - m_track->setHeight( DEFAULT_TRACK_HEIGHT ); - } - - - int widgetTotal = ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt()==1 ? - DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : - DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH; - if( m_trackContainerView->allowRubberband() == true && me->x() > widgetTotal ) - { - QWidget::mousePressEvent( me ); - } - else if( me->button() == Qt::LeftButton ) - { - if( me->modifiers() & Qt::ShiftModifier ) - { - m_action = ResizeTrack; - QCursor::setPos( mapToGlobal( QPoint( me->x(), - height() ) ) ); - QCursor c( Qt::SizeVerCursor); - QApplication::setOverrideCursor( c ); - } - else - { - if( me->x()>10 ) // 10 = The width of the grip + 2 pixels to the left and right. - { - QWidget::mousePressEvent( me ); - return; - } - - m_action = MoveTrack; - - QCursor c( Qt::SizeVerCursor ); - QApplication::setOverrideCursor( c ); - // update because in move-mode, all elements in - // track-op-widgets are hidden as a visual feedback - m_trackOperationsWidget.update(); - } - - me->accept(); - } - else - { - QWidget::mousePressEvent( me ); - } -} - - - - -/*! \brief Handle a mouse move event on this track View. - * - * If this track container supports rubber band selection, let the - * widget handle that and don't bother with any other handling. - * - * Otherwise if we've started the move process (from mousePressEvent()) - * then move ourselves into that position, reordering the track list - * with moveTrackViewUp() and moveTrackViewDown() to suit. We make a - * note of this in the undo journal in case the user wants to undo this - * move. - * - * Likewise if we've started a resize process, handle this too, making - * sure that we never go below the minimum track height. - * - * \param me the MouseEvent to handle. - */ -void TrackView::mouseMoveEvent( QMouseEvent * me ) -{ - int widgetTotal = ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt()==1 ? - DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : - DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH; - if( m_trackContainerView->allowRubberband() == true && me->x() > widgetTotal ) - { - QWidget::mouseMoveEvent( me ); - } - else if( m_action == MoveTrack ) - { - // look which track-widget the mouse-cursor is over - const int yPos = - m_trackContainerView->contentWidget()->mapFromGlobal( me->globalPos() ).y(); - const TrackView * trackAtY = m_trackContainerView->trackViewAt( yPos ); - - // debug code - // qDebug( "y position %d", yPos ); - - // a track-widget not equal to ourself? - if( trackAtY != NULL && trackAtY != this ) - { - // then move us up/down there! - if( me->y() < 0 ) - { - m_trackContainerView->moveTrackViewUp( this ); - } - else - { - m_trackContainerView->moveTrackViewDown( this ); - } - } - } - else if( m_action == ResizeTrack ) - { - setFixedHeight( qMax( me->y(), MINIMAL_TRACK_HEIGHT ) ); - m_trackContainerView->realignTracks(); - m_track->setHeight( height() ); - } - - if( height() < DEFAULT_TRACK_HEIGHT ) - { - ToolTip::add( this, m_track->m_name ); - } -} - - - -/*! \brief Handle a mouse release event on this track View. - * - * \param me the MouseEvent to handle. - */ -void TrackView::mouseReleaseEvent( QMouseEvent * me ) -{ - m_action = NoAction; - while( QApplication::overrideCursor() != NULL ) - { - QApplication::restoreOverrideCursor(); - } - m_trackOperationsWidget.update(); - - QWidget::mouseReleaseEvent( me ); -} - - - - -/*! \brief Repaint this track View. - * - * \param pe the PaintEvent to start. - */ -void TrackView::paintEvent( QPaintEvent * pe ) -{ - QStyleOption opt; - opt.initFrom( this ); - QPainter p( this ); - style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); -} - - - - -/*! \brief Create a TrackContentObject View in this track View. - * - * \param tco the TrackContentObject to create the view for. - * \todo is this a good description for what this method does? - */ -void TrackView::createTCOView( TrackContentObject * tco ) -{ - TrackContentObjectView * tv = tco->createView( this ); - if( tco->getSelectViewOnCreate() == true ) - { - tv->setSelected( true ); - } - tco->selectViewOnCreate( false ); -} - - - - -void TrackView::muteChanged() -{ - FadeButton * indicator = getActivityIndicator(); - if (indicator) { setIndicatorMute(indicator, m_track->m_mutedModel.value()); } -} - - - - -void TrackView::setIndicatorMute(FadeButton* indicator, bool muted) -{ - QPalette::ColorRole role = muted ? QPalette::Highlight : QPalette::BrightText; - indicator->setActiveColor(QApplication::palette().color(QPalette::Active, role)); -} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e1be929aab1..ab80e607178 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -34,6 +34,7 @@ SET(LMMS_SRCS gui/TimeLineWidget.cpp gui/ToolPluginView.cpp gui/TrackContainerView.cpp + gui/TrackView.cpp gui/dialogs/FileDialog.cpp gui/dialogs/VersionedSaveDialog.cpp diff --git a/src/gui/TrackView.cpp b/src/gui/TrackView.cpp new file mode 100644 index 00000000000..a38c757cb75 --- /dev/null +++ b/src/gui/TrackView.cpp @@ -0,0 +1,473 @@ +/* + * TrackView.cpp - implementation of TrackView class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#include "TrackView.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "AutomationPattern.h" +#include "AutomationTrack.h" +#include "AutomationEditor.h" +#include "BBEditor.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" +#include "ConfigManager.h" +#include "Clipboard.h" +#include "ColorChooser.h" +#include "embed.h" +#include "Engine.h" +#include "GuiApplication.h" +#include "FxMixerView.h" +#include "gui_templates.h" +#include "MainWindow.h" +#include "Mixer.h" +#include "ProjectJournal.h" +#include "SampleTrack.h" +#include "Song.h" +#include "SongEditor.h" +#include "StringPairDrag.h" +#include "TextFloat.h" + + +/*! \brief Create a new track View. + * + * The track View is handles the actual display of the track, including + * displaying its various widgets and the track segments. + * + * \param track The track to display. + * \param tcv The track Container View for us to be displayed in. + * \todo Is my description of these properties correct? + */ +TrackView::TrackView( Track * track, TrackContainerView * tcv ) : + QWidget( tcv->contentWidget() ), /*!< The Track Container View's content widget. */ + ModelView( NULL, this ), /*!< The model view of this track */ + m_track( track ), /*!< The track we're displaying */ + m_trackContainerView( tcv ), /*!< The track Container View we're displayed in */ + m_trackOperationsWidget( this ), /*!< Our trackOperationsWidget */ + m_trackSettingsWidget( this ), /*!< Our trackSettingsWidget */ + m_trackContentWidget( this ), /*!< Our trackContentWidget */ + m_action( NoAction ) /*!< The action we're currently performing */ +{ + setAutoFillBackground( true ); + QPalette pal; + pal.setColor( backgroundRole(), QColor( 32, 36, 40 ) ); + setPalette( pal ); + + m_trackSettingsWidget.setAutoFillBackground( true ); + + QHBoxLayout * layout = new QHBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addWidget( &m_trackOperationsWidget ); + layout->addWidget( &m_trackSettingsWidget ); + layout->addWidget( &m_trackContentWidget, 1 ); + setFixedHeight( m_track->getHeight() ); + + resizeEvent( NULL ); + + setAcceptDrops( true ); + setAttribute( Qt::WA_DeleteOnClose, true ); + + + connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); + connect( m_track, + SIGNAL( trackContentObjectAdded( TrackContentObject * ) ), + this, SLOT( createTCOView( TrackContentObject * ) ), + Qt::QueuedConnection ); + + connect( &m_track->m_mutedModel, SIGNAL( dataChanged() ), + &m_trackContentWidget, SLOT( update() ) ); + + connect(&m_track->m_mutedModel, SIGNAL(dataChanged()), + this, SLOT(muteChanged())); + + connect( &m_track->m_soloModel, SIGNAL( dataChanged() ), + m_track, SLOT( toggleSolo() ), Qt::DirectConnection ); + + connect( &m_trackOperationsWidget, SIGNAL( colorChanged( QColor & ) ), + m_track, SLOT( trackColorChanged( QColor & ) ) ); + + connect( &m_trackOperationsWidget, SIGNAL( colorReset() ), + m_track, SLOT( trackColorReset() ) ); + + // create views for already existing TCOs + for( Track::tcoVector::iterator it = + m_track->m_trackContentObjects.begin(); + it != m_track->m_trackContentObjects.end(); ++it ) + { + createTCOView( *it ); + } + + m_trackContainerView->addTrackView( this ); +} + + + + +/*! \brief Destroy this track View. + * + */ +TrackView::~TrackView() +{ +} + + + + +/*! \brief Resize this track View. + * + * \param re the Resize Event to handle. + */ +void TrackView::resizeEvent( QResizeEvent * re ) +{ + if( ConfigManager::inst()->value( "ui", + "compacttrackbuttons" ).toInt() ) + { + m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH_COMPACT, height() - 1 ); + m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT, height() - 1 ); + } + else + { + m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH, height() - 1 ); + m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH, height() - 1 ); + } + m_trackContentWidget.setFixedHeight( height() ); +} + + + + +/*! \brief Update this track View and all its content objects. + * + */ +void TrackView::update() +{ + m_trackContentWidget.update(); + if( !m_trackContainerView->fixedTCOs() ) + { + m_trackContentWidget.changePosition(); + } + QWidget::update(); +} + + + + +/*! \brief Create a menu for assigning/creating channels for this track. + * + */ +QMenu * TrackView::createFxMenu(QString title, QString newFxLabel) +{ + Q_UNUSED(title) + Q_UNUSED(newFxLabel) + return NULL; +} + + + + +/*! \brief Close this track View. + * + */ +bool TrackView::close() +{ + m_trackContainerView->removeTrackView( this ); + return QWidget::close(); +} + + + + +/*! \brief Register that the model of this track View has changed. + * + */ +void TrackView::modelChanged() +{ + m_track = castModel(); + assert( m_track != NULL ); + connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); + m_trackOperationsWidget.m_muteBtn->setModel( &m_track->m_mutedModel ); + m_trackOperationsWidget.m_soloBtn->setModel( &m_track->m_soloModel ); + ModelView::modelChanged(); + setFixedHeight( m_track->getHeight() ); +} + + + + +/*! \brief Start a drag event on this track View. + * + * \param dee the DragEnterEvent to start. + */ +void TrackView::dragEnterEvent( QDragEnterEvent * dee ) +{ + StringPairDrag::processDragEnterEvent( dee, "track_" + + QString::number( m_track->type() ) ); +} + + + + +/*! \brief Accept a drop event on this track View. + * + * We only accept drop events that are of the same type as this track. + * If so, we decode the data from the drop event by just feeding it + * back into the engine as a state. + * + * \param de the DropEvent to handle. + */ +void TrackView::dropEvent( QDropEvent * de ) +{ + QString type = StringPairDrag::decodeKey( de ); + QString value = StringPairDrag::decodeValue( de ); + if( type == ( "track_" + QString::number( m_track->type() ) ) ) + { + // value contains our XML-data so simply create a + // DataFile which does the rest for us... + DataFile dataFile( value.toUtf8() ); + Engine::mixer()->requestChangeInModel(); + m_track->restoreState( dataFile.content().firstChild().toElement() ); + Engine::mixer()->doneChangeInModel(); + de->accept(); + } +} + + + + +/*! \brief Handle a mouse press event on this track View. + * + * If this track container supports rubber band selection, let the + * widget handle that and don't bother with any other handling. + * + * If the left mouse button is pressed, we handle two things. If + * SHIFT is pressed, then we resize vertically. Otherwise we start + * the process of moving this track to a new position. + * + * Otherwise we let the widget handle the mouse event as normal. + * + * \param me the MouseEvent to handle. + */ +void TrackView::mousePressEvent( QMouseEvent * me ) +{ + + // If previously dragged too small, restore on shift-leftclick + if( height() < DEFAULT_TRACK_HEIGHT && + me->modifiers() & Qt::ShiftModifier && + me->button() == Qt::LeftButton ) + { + setFixedHeight( DEFAULT_TRACK_HEIGHT ); + m_track->setHeight( DEFAULT_TRACK_HEIGHT ); + } + + + int widgetTotal = ConfigManager::inst()->value( "ui", + "compacttrackbuttons" ).toInt()==1 ? + DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : + DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH; + if( m_trackContainerView->allowRubberband() == true && me->x() > widgetTotal ) + { + QWidget::mousePressEvent( me ); + } + else if( me->button() == Qt::LeftButton ) + { + if( me->modifiers() & Qt::ShiftModifier ) + { + m_action = ResizeTrack; + QCursor::setPos( mapToGlobal( QPoint( me->x(), + height() ) ) ); + QCursor c( Qt::SizeVerCursor); + QApplication::setOverrideCursor( c ); + } + else + { + if( me->x()>10 ) // 10 = The width of the grip + 2 pixels to the left and right. + { + QWidget::mousePressEvent( me ); + return; + } + + m_action = MoveTrack; + + QCursor c( Qt::SizeVerCursor ); + QApplication::setOverrideCursor( c ); + // update because in move-mode, all elements in + // track-op-widgets are hidden as a visual feedback + m_trackOperationsWidget.update(); + } + + me->accept(); + } + else + { + QWidget::mousePressEvent( me ); + } +} + + + + +/*! \brief Handle a mouse move event on this track View. + * + * If this track container supports rubber band selection, let the + * widget handle that and don't bother with any other handling. + * + * Otherwise if we've started the move process (from mousePressEvent()) + * then move ourselves into that position, reordering the track list + * with moveTrackViewUp() and moveTrackViewDown() to suit. We make a + * note of this in the undo journal in case the user wants to undo this + * move. + * + * Likewise if we've started a resize process, handle this too, making + * sure that we never go below the minimum track height. + * + * \param me the MouseEvent to handle. + */ +void TrackView::mouseMoveEvent( QMouseEvent * me ) +{ + int widgetTotal = ConfigManager::inst()->value( "ui", + "compacttrackbuttons" ).toInt()==1 ? + DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : + DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH; + if( m_trackContainerView->allowRubberband() == true && me->x() > widgetTotal ) + { + QWidget::mouseMoveEvent( me ); + } + else if( m_action == MoveTrack ) + { + // look which track-widget the mouse-cursor is over + const int yPos = + m_trackContainerView->contentWidget()->mapFromGlobal( me->globalPos() ).y(); + const TrackView * trackAtY = m_trackContainerView->trackViewAt( yPos ); + + // debug code + // qDebug( "y position %d", yPos ); + + // a track-widget not equal to ourself? + if( trackAtY != NULL && trackAtY != this ) + { + // then move us up/down there! + if( me->y() < 0 ) + { + m_trackContainerView->moveTrackViewUp( this ); + } + else + { + m_trackContainerView->moveTrackViewDown( this ); + } + } + } + else if( m_action == ResizeTrack ) + { + setFixedHeight( qMax( me->y(), MINIMAL_TRACK_HEIGHT ) ); + m_trackContainerView->realignTracks(); + m_track->setHeight( height() ); + } + + if( height() < DEFAULT_TRACK_HEIGHT ) + { + ToolTip::add( this, m_track->m_name ); + } +} + + + +/*! \brief Handle a mouse release event on this track View. + * + * \param me the MouseEvent to handle. + */ +void TrackView::mouseReleaseEvent( QMouseEvent * me ) +{ + m_action = NoAction; + while( QApplication::overrideCursor() != NULL ) + { + QApplication::restoreOverrideCursor(); + } + m_trackOperationsWidget.update(); + + QWidget::mouseReleaseEvent( me ); +} + + + + +/*! \brief Repaint this track View. + * + * \param pe the PaintEvent to start. + */ +void TrackView::paintEvent( QPaintEvent * pe ) +{ + QStyleOption opt; + opt.initFrom( this ); + QPainter p( this ); + style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); +} + + + + +/*! \brief Create a TrackContentObject View in this track View. + * + * \param tco the TrackContentObject to create the view for. + * \todo is this a good description for what this method does? + */ +void TrackView::createTCOView( TrackContentObject * tco ) +{ + TrackContentObjectView * tv = tco->createView( this ); + if( tco->getSelectViewOnCreate() == true ) + { + tv->setSelected( true ); + } + tco->selectViewOnCreate( false ); +} + + + + +void TrackView::muteChanged() +{ + FadeButton * indicator = getActivityIndicator(); + if (indicator) { setIndicatorMute(indicator, m_track->m_mutedModel.value()); } +} + + + + +void TrackView::setIndicatorMute(FadeButton* indicator, bool muted) +{ + QPalette::ColorRole role = muted ? QPalette::Highlight : QPalette::BrightText; + indicator->setActiveColor(QApplication::palette().color(QPalette::Active, role)); +} diff --git a/src/gui/widgets/FxLineLcdSpinBox.cpp b/src/gui/widgets/FxLineLcdSpinBox.cpp index bfe4a9637f9..be9e617fdb7 100644 --- a/src/gui/widgets/FxLineLcdSpinBox.cpp +++ b/src/gui/widgets/FxLineLcdSpinBox.cpp @@ -28,6 +28,7 @@ #include "FxMixerView.h" #include "GuiApplication.h" #include "Track.h" +#include "TrackView.h" void FxLineLcdSpinBox::setTrackView(TrackView * tv) { From 32c356ba2b91133dbfff6f9f8dc01302a096bda5 Mon Sep 17 00:00:00 2001 From: M374LX Date: Wed, 25 Nov 2020 23:03:17 -0300 Subject: [PATCH 3/6] Split TrackOperationsWidget off of Track --- include/Track.h | 54 +--- include/TrackOperationsWidget.h | 79 +++++ include/TrackView.h | 1 + src/core/Track.cpp | 322 +------------------- src/gui/CMakeLists.txt | 1 + src/gui/widgets/TrackOperationsWidget.cpp | 351 ++++++++++++++++++++++ 6 files changed, 435 insertions(+), 373 deletions(-) create mode 100644 include/TrackOperationsWidget.h create mode 100644 src/gui/widgets/TrackOperationsWidget.cpp diff --git a/include/Track.h b/include/Track.h index 9efc2e34ea0..364939c862b 100644 --- a/include/Track.h +++ b/include/Track.h @@ -27,7 +27,6 @@ #define TRACK_H #include -#include #include #include #include @@ -35,12 +34,9 @@ #include "lmms_basics.h" #include "MidiTime.h" -#include "Rubberband.h" #include "JournallingObject.h" -#include "AutomatableModel.h" #include "ModelView.h" #include "DataFile.h" -#include "FadeButton.h" #include "TrackContentObject.h" @@ -54,6 +50,7 @@ class TrackContentObjectView; class TrackContainer; class TrackContainerView; class TrackContentWidget; +class TrackView; const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; @@ -354,55 +351,6 @@ public slots: - -class TrackOperationsWidget : public QWidget -{ - Q_OBJECT -public: - TrackOperationsWidget( TrackView * parent ); - ~TrackOperationsWidget(); - - -protected: - void mousePressEvent( QMouseEvent * me ) override; - void paintEvent( QPaintEvent * pe ) override; - - -private slots: - void cloneTrack(); - void removeTrack(); - void updateMenu(); - void changeTrackColor(); - void randomTrackColor(); - void resetTrackColor(); - void useTrackColor(); - void toggleRecording(bool on); - void recordingOn(); - void recordingOff(); - void clearTrack(); - -private: - TrackView * m_trackView; - - QPushButton * m_trackOps; - PixmapButton * m_muteBtn; - PixmapButton * m_soloBtn; - - - friend class TrackView; - -signals: - void trackRemovalScheduled( TrackView * t ); - void colorChanged( QColor & c ); - void colorParented(); - void colorReset(); - -} ; - - - - - // base-class for all tracks class LMMS_EXPORT Track : public Model, public JournallingObject { diff --git a/include/TrackOperationsWidget.h b/include/TrackOperationsWidget.h new file mode 100644 index 00000000000..f78e0a2092a --- /dev/null +++ b/include/TrackOperationsWidget.h @@ -0,0 +1,79 @@ +/* + * TrackOperationsWidget.h - declaration of TrackOperationsWidget class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef TRACK_OPERATIONS_WIDGET_H +#define TRACK_OPERATIONS_WIDGET_H + +#include + +class QPushButton; + +class PixmapButton; +class TrackView; + +class TrackOperationsWidget : public QWidget +{ + Q_OBJECT +public: + TrackOperationsWidget( TrackView * parent ); + ~TrackOperationsWidget(); + + +protected: + void mousePressEvent( QMouseEvent * me ) override; + void paintEvent( QPaintEvent * pe ) override; + + +private slots: + void cloneTrack(); + void removeTrack(); + void updateMenu(); + void changeTrackColor(); + void randomTrackColor(); + void resetTrackColor(); + void useTrackColor(); + void toggleRecording(bool on); + void recordingOn(); + void recordingOff(); + void clearTrack(); + +private: + TrackView * m_trackView; + + QPushButton * m_trackOps; + PixmapButton * m_muteBtn; + PixmapButton * m_soloBtn; + + + friend class TrackView; + +signals: + void trackRemovalScheduled( TrackView * t ); + void colorChanged( QColor & c ); + void colorParented(); + void colorReset(); + +} ; + +#endif diff --git a/include/TrackView.h b/include/TrackView.h index 16ebd10ac4d..1dd3c50c069 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -32,6 +32,7 @@ #include "JournallingObject.h" #include "ModelView.h" #include "Track.h" +#include "TrackOperationsWidget.h" class TrackView : public QWidget, public ModelView, public JournallingObject diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 8c42cb9d1f6..f4e3e8ca693 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -40,33 +40,23 @@ #include #include -#include #include #include #include #include -#include #include -#include #include "AutomationPattern.h" #include "AutomationTrack.h" -#include "AutomationEditor.h" #include "BBEditor.h" #include "BBTrack.h" #include "BBTrackContainer.h" #include "ConfigManager.h" #include "Clipboard.h" #include "ColorChooser.h" -#include "embed.h" #include "Engine.h" #include "GuiApplication.h" -#include "FxMixerView.h" -#include "gui_templates.h" -#include "MainWindow.h" -#include "Mixer.h" -#include "ProjectJournal.h" #include "SampleTrack.h" #include "Song.h" #include "SongEditor.h" @@ -76,11 +66,11 @@ /*! The width of the resize grip in pixels */ -const int RESIZE_GRIP_WIDTH = 4; +const int RESIZE_GRIP_WIDTH = 4; //TrackContentObjectView /*! Alternate between a darker and a lighter background color every 4 bars */ -const int BARS_PER_GROUP = 4; +const int BARS_PER_GROUP = 4; //TrackContentWidget /*! A pointer for that text bubble used when moving segments, etc. @@ -1945,314 +1935,6 @@ void TrackContentWidget::setEmbossColor( const QBrush & c ) { m_embossColor = c; } -// =========================================================================== -// trackOperationsWidget -// =========================================================================== - -/*! \brief Create a new trackOperationsWidget - * - * The trackOperationsWidget is the grip and the mute button of a track. - * - * \param parent the trackView to contain this widget - */ -TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : - QWidget( parent ), /*!< The parent widget */ - m_trackView( parent ) /*!< The parent track view */ -{ - ToolTip::add( this, tr( "Press <%1> while clicking on move-grip " - "to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) ); - - QMenu * toMenu = new QMenu( this ); - toMenu->setFont( pointSize<9>( toMenu->font() ) ); - connect( toMenu, SIGNAL( aboutToShow() ), this, SLOT( updateMenu() ) ); - - - setObjectName( "automationEnabled" ); - - - m_trackOps = new QPushButton( this ); - m_trackOps->move( 12, 1 ); - m_trackOps->setFocusPolicy( Qt::NoFocus ); - m_trackOps->setMenu( toMenu ); - ToolTip::add( m_trackOps, tr( "Actions" ) ); - - - m_muteBtn = new PixmapButton( this, tr( "Mute" ) ); - m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); - m_muteBtn->setCheckable( true ); - - m_soloBtn = new PixmapButton( this, tr( "Solo" ) ); - m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) ); - m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_soloBtn->setCheckable( true ); - - if( ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt() ) - { - m_muteBtn->move( 46, 0 ); - m_soloBtn->move( 46, 16 ); - } - else - { - m_muteBtn->move( 46, 8 ); - m_soloBtn->move( 62, 8 ); - } - - m_muteBtn->show(); - ToolTip::add( m_muteBtn, tr( "Mute" ) ); - - m_soloBtn->show(); - ToolTip::add( m_soloBtn, tr( "Solo" ) ); - - connect( this, SIGNAL( trackRemovalScheduled( TrackView * ) ), - m_trackView->trackContainerView(), - SLOT( deleteTrackView( TrackView * ) ), - Qt::QueuedConnection ); - - connect( m_trackView->getTrack()->getMutedModel(), SIGNAL( dataChanged() ), - this, SLOT( update() ) ); - -} - - - - -/*! \brief Destroy an existing trackOperationsWidget - * - */ -TrackOperationsWidget::~TrackOperationsWidget() -{ -} - - - - -/*! \brief Respond to trackOperationsWidget mouse events - * - * If it's the left mouse button, and Ctrl is held down, and we're - * not a Beat+Bassline Editor track, then start a new drag event to - * copy this track. - * - * Otherwise, ignore all other events. - * - * \param me The mouse event to respond to. - */ -void TrackOperationsWidget::mousePressEvent( QMouseEvent * me ) -{ - if( me->button() == Qt::LeftButton && - me->modifiers() & Qt::ControlModifier && - m_trackView->getTrack()->type() != Track::BBTrack ) - { - DataFile dataFile( DataFile::DragNDropData ); - m_trackView->getTrack()->saveState( dataFile, dataFile.content() ); - new StringPairDrag( QString( "track_%1" ).arg( - m_trackView->getTrack()->type() ), - dataFile.toString(), m_trackView->getTrackSettingsWidget()->grab(), - this ); - } - else if( me->button() == Qt::LeftButton ) - { - // track-widget (parent-widget) initiates track-move - me->ignore(); - } -} - - - - -/*! \brief Repaint the trackOperationsWidget - * - * If we're not moving, and in the Beat+Bassline Editor, then turn - * automation on or off depending on its previous state and show - * ourselves. - * - * Otherwise, hide ourselves. - * - * \todo Flesh this out a bit - is it correct? - * \param pe The paint event to respond to - */ -void TrackOperationsWidget::paintEvent( QPaintEvent * pe ) -{ - QPainter p( this ); - - p.fillRect( rect(), palette().brush(QPalette::Background) ); - - if( m_trackView->getTrack()->useColor() && ! m_trackView->getTrack()->getMutedModel()->value() ) - { - QRect coloredRect( 0, 0, 10, m_trackView->getTrack()->getHeight() ); - - p.fillRect( coloredRect, m_trackView->getTrack()->color() ); - } - - if( m_trackView->isMovingTrack() == false ) - { - p.drawPixmap( 2, 2, embed::getIconPixmap("track_op_grip")); - } - else - { - p.drawPixmap( 2, 2, embed::getIconPixmap("track_op_grip_c")); - } -} - - - - -/*! \brief Clone this track - * - */ -void TrackOperationsWidget::cloneTrack() -{ - TrackContainerView *tcView = m_trackView->trackContainerView(); - - Track *newTrack = m_trackView->getTrack()->clone(); - TrackView *newTrackView = tcView->createTrackView( newTrack ); - - int index = tcView->trackViews().indexOf( m_trackView ); - int i = tcView->trackViews().size(); - while ( i != index + 1 ) - { - tcView->moveTrackView( newTrackView, i - 1 ); - i--; - } -} - - -/*! \brief Clear this track - clears all TCOs from the track */ -void TrackOperationsWidget::clearTrack() -{ - Track * t = m_trackView->getTrack(); - t->addJournalCheckPoint(); - t->lock(); - t->deleteTCOs(); - t->unlock(); -} - - - -/*! \brief Remove this track from the track list - * - */ -void TrackOperationsWidget::removeTrack() -{ - emit trackRemovalScheduled( m_trackView ); -} - -void TrackOperationsWidget::changeTrackColor() -{ - QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )-> \ - getColor( m_trackView->getTrack()->color() ); - - if( ! new_color.isValid() ) - { return; } - - emit colorChanged( new_color ); - - Engine::getSong()->setModified(); - update(); -} - -void TrackOperationsWidget::resetTrackColor() -{ - emit colorReset(); - Engine::getSong()->setModified(); - update(); -} - -void TrackOperationsWidget::randomTrackColor() -{ - QColor buffer = ColorChooser::getPalette( ColorChooser::Palette::Track )[ rand() % 48 ]; - - emit colorChanged( buffer ); - Engine::getSong()->setModified(); - update(); -} - -void TrackOperationsWidget::useTrackColor() -{ - emit colorParented(); - Engine::getSong()->setModified(); -} - - -/*! \brief Update the trackOperationsWidget context menu - * - * For all track types, we have the Clone and Remove options. - * For instrument-tracks we also offer the MIDI-control-menu - * For automation tracks, extra options: turn on/off recording - * on all TCOs (same should be added for sample tracks when - * sampletrack recording is implemented) - */ -void TrackOperationsWidget::updateMenu() -{ - QMenu * toMenu = m_trackOps->menu(); - toMenu->clear(); - toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), - tr( "Clone this track" ), - this, SLOT( cloneTrack() ) ); - toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), - tr( "Remove this track" ), - this, SLOT( removeTrack() ) ); - - if( ! m_trackView->trackContainerView()->fixedTCOs() ) - { - toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) ); - } - if (QMenu *fxMenu = m_trackView->createFxMenu(tr("FX %1: %2"), tr("Assign to new FX Channel"))) - { - toMenu->addMenu(fxMenu); - } - - if (InstrumentTrackView * trackView = dynamic_cast(m_trackView)) - { - toMenu->addSeparator(); - toMenu->addMenu(trackView->midiMenu()); - } - if( dynamic_cast( m_trackView ) ) - { - toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); - toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); - } - - toMenu->addSeparator(); - toMenu->addAction( embed::getIconPixmap( "colorize" ), - tr( "Change color" ), this, SLOT( changeTrackColor() ) ); - toMenu->addAction( embed::getIconPixmap( "colorize" ), - tr( "Reset color to default" ), this, SLOT( resetTrackColor() ) ); - toMenu->addAction( embed::getIconPixmap( "colorize" ), - tr( "Set random color" ), this, SLOT( randomTrackColor() ) ); - toMenu->addSeparator(); - toMenu->addAction( embed::getIconPixmap( "colorize" ), - tr( "Clear clip colors" ), this, SLOT( useTrackColor() ) ); -} - - -void TrackOperationsWidget::toggleRecording( bool on ) -{ - AutomationTrackView * atv = dynamic_cast( m_trackView ); - if( atv ) - { - for( TrackContentObject * tco : atv->getTrack()->getTCOs() ) - { - AutomationPattern * ap = dynamic_cast( tco ); - if( ap ) { ap->setRecording( on ); } - } - atv->update(); - } -} - - - -void TrackOperationsWidget::recordingOn() -{ - toggleRecording( true ); -} - - -void TrackOperationsWidget::recordingOff() -{ - toggleRecording( false ); -} // =========================================================================== diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ab80e607178..40d8f3e2181 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -98,6 +98,7 @@ SET(LMMS_SRCS gui/widgets/ToolButton.cpp gui/widgets/ToolTip.cpp gui/widgets/TrackLabelButton.cpp + gui/widgets/TrackOperationsWidget.cpp gui/widgets/TrackRenameLineEdit.cpp gui/widgets/StepRecorderWidget.cpp diff --git a/src/gui/widgets/TrackOperationsWidget.cpp b/src/gui/widgets/TrackOperationsWidget.cpp new file mode 100644 index 00000000000..7983d767802 --- /dev/null +++ b/src/gui/widgets/TrackOperationsWidget.cpp @@ -0,0 +1,351 @@ +/* + * TrackOperationsWidget.cpp - implementation of TrackOperationsWidget class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "TrackOperationsWidget.h" + +#include +#include +#include +#include + +#include "AutomationPattern.h" +#include "AutomationTrack.h" +#include "ColorChooser.h" +#include "ConfigManager.h" +#include "embed.h" +#include "Engine.h" +#include "gui_templates.h" +#include "PixmapButton.h" +#include "Song.h" +#include "StringPairDrag.h" +#include "ToolTip.h" +#include "Track.h" +#include "TrackContainerView.h" +#include "TrackView.h" + +/*! \brief Create a new trackOperationsWidget + * + * The trackOperationsWidget is the grip and the mute button of a track. + * + * \param parent the trackView to contain this widget + */ +TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : + QWidget( parent ), /*!< The parent widget */ + m_trackView( parent ) /*!< The parent track view */ +{ + ToolTip::add( this, tr( "Press <%1> while clicking on move-grip " + "to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) ); + + QMenu * toMenu = new QMenu( this ); + toMenu->setFont( pointSize<9>( toMenu->font() ) ); + connect( toMenu, SIGNAL( aboutToShow() ), this, SLOT( updateMenu() ) ); + + + setObjectName( "automationEnabled" ); + + + m_trackOps = new QPushButton( this ); + m_trackOps->move( 12, 1 ); + m_trackOps->setFocusPolicy( Qt::NoFocus ); + m_trackOps->setMenu( toMenu ); + ToolTip::add( m_trackOps, tr( "Actions" ) ); + + + m_muteBtn = new PixmapButton( this, tr( "Mute" ) ); + m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); + m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); + m_muteBtn->setCheckable( true ); + + m_soloBtn = new PixmapButton( this, tr( "Solo" ) ); + m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) ); + m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); + m_soloBtn->setCheckable( true ); + + if( ConfigManager::inst()->value( "ui", + "compacttrackbuttons" ).toInt() ) + { + m_muteBtn->move( 46, 0 ); + m_soloBtn->move( 46, 16 ); + } + else + { + m_muteBtn->move( 46, 8 ); + m_soloBtn->move( 62, 8 ); + } + + m_muteBtn->show(); + ToolTip::add( m_muteBtn, tr( "Mute" ) ); + + m_soloBtn->show(); + ToolTip::add( m_soloBtn, tr( "Solo" ) ); + + connect( this, SIGNAL( trackRemovalScheduled( TrackView * ) ), + m_trackView->trackContainerView(), + SLOT( deleteTrackView( TrackView * ) ), + Qt::QueuedConnection ); + + connect( m_trackView->getTrack()->getMutedModel(), SIGNAL( dataChanged() ), + this, SLOT( update() ) ); + +} + + + + +/*! \brief Destroy an existing trackOperationsWidget + * + */ +TrackOperationsWidget::~TrackOperationsWidget() +{ +} + + + + +/*! \brief Respond to trackOperationsWidget mouse events + * + * If it's the left mouse button, and Ctrl is held down, and we're + * not a Beat+Bassline Editor track, then start a new drag event to + * copy this track. + * + * Otherwise, ignore all other events. + * + * \param me The mouse event to respond to. + */ +void TrackOperationsWidget::mousePressEvent( QMouseEvent * me ) +{ + if( me->button() == Qt::LeftButton && + me->modifiers() & Qt::ControlModifier && + m_trackView->getTrack()->type() != Track::BBTrack ) + { + DataFile dataFile( DataFile::DragNDropData ); + m_trackView->getTrack()->saveState( dataFile, dataFile.content() ); + new StringPairDrag( QString( "track_%1" ).arg( + m_trackView->getTrack()->type() ), + dataFile.toString(), m_trackView->getTrackSettingsWidget()->grab(), + this ); + } + else if( me->button() == Qt::LeftButton ) + { + // track-widget (parent-widget) initiates track-move + me->ignore(); + } +} + + + + +/*! \brief Repaint the trackOperationsWidget + * + * If we're not moving, and in the Beat+Bassline Editor, then turn + * automation on or off depending on its previous state and show + * ourselves. + * + * Otherwise, hide ourselves. + * + * \todo Flesh this out a bit - is it correct? + * \param pe The paint event to respond to + */ +void TrackOperationsWidget::paintEvent( QPaintEvent * pe ) +{ + QPainter p( this ); + + p.fillRect( rect(), palette().brush(QPalette::Background) ); + + if( m_trackView->getTrack()->useColor() && ! m_trackView->getTrack()->getMutedModel()->value() ) + { + QRect coloredRect( 0, 0, 10, m_trackView->getTrack()->getHeight() ); + + p.fillRect( coloredRect, m_trackView->getTrack()->color() ); + } + + if( m_trackView->isMovingTrack() == false ) + { + p.drawPixmap( 2, 2, embed::getIconPixmap("track_op_grip")); + } + else + { + p.drawPixmap( 2, 2, embed::getIconPixmap("track_op_grip_c")); + } +} + + + + +/*! \brief Clone this track + * + */ +void TrackOperationsWidget::cloneTrack() +{ + TrackContainerView *tcView = m_trackView->trackContainerView(); + + Track *newTrack = m_trackView->getTrack()->clone(); + TrackView *newTrackView = tcView->createTrackView( newTrack ); + + int index = tcView->trackViews().indexOf( m_trackView ); + int i = tcView->trackViews().size(); + while ( i != index + 1 ) + { + tcView->moveTrackView( newTrackView, i - 1 ); + i--; + } +} + + +/*! \brief Clear this track - clears all TCOs from the track */ +void TrackOperationsWidget::clearTrack() +{ + Track * t = m_trackView->getTrack(); + t->addJournalCheckPoint(); + t->lock(); + t->deleteTCOs(); + t->unlock(); +} + + + +/*! \brief Remove this track from the track list + * + */ +void TrackOperationsWidget::removeTrack() +{ + emit trackRemovalScheduled( m_trackView ); +} + +void TrackOperationsWidget::changeTrackColor() +{ + QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )-> \ + getColor( m_trackView->getTrack()->color() ); + + if( ! new_color.isValid() ) + { return; } + + emit colorChanged( new_color ); + + Engine::getSong()->setModified(); + update(); +} + +void TrackOperationsWidget::resetTrackColor() +{ + emit colorReset(); + Engine::getSong()->setModified(); + update(); +} + +void TrackOperationsWidget::randomTrackColor() +{ + QColor buffer = ColorChooser::getPalette( ColorChooser::Palette::Track )[ rand() % 48 ]; + + emit colorChanged( buffer ); + Engine::getSong()->setModified(); + update(); +} + +void TrackOperationsWidget::useTrackColor() +{ + emit colorParented(); + Engine::getSong()->setModified(); +} + + +/*! \brief Update the trackOperationsWidget context menu + * + * For all track types, we have the Clone and Remove options. + * For instrument-tracks we also offer the MIDI-control-menu + * For automation tracks, extra options: turn on/off recording + * on all TCOs (same should be added for sample tracks when + * sampletrack recording is implemented) + */ +void TrackOperationsWidget::updateMenu() +{ + QMenu * toMenu = m_trackOps->menu(); + toMenu->clear(); + toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), + tr( "Clone this track" ), + this, SLOT( cloneTrack() ) ); + toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), + tr( "Remove this track" ), + this, SLOT( removeTrack() ) ); + + if( ! m_trackView->trackContainerView()->fixedTCOs() ) + { + toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) ); + } + if (QMenu *fxMenu = m_trackView->createFxMenu(tr("FX %1: %2"), tr("Assign to new FX Channel"))) + { + toMenu->addMenu(fxMenu); + } + + if (InstrumentTrackView * trackView = dynamic_cast(m_trackView)) + { + toMenu->addSeparator(); + toMenu->addMenu(trackView->midiMenu()); + } + if( dynamic_cast( m_trackView ) ) + { + toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); + toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); + } + + toMenu->addSeparator(); + toMenu->addAction( embed::getIconPixmap( "colorize" ), + tr( "Change color" ), this, SLOT( changeTrackColor() ) ); + toMenu->addAction( embed::getIconPixmap( "colorize" ), + tr( "Reset color to default" ), this, SLOT( resetTrackColor() ) ); + toMenu->addAction( embed::getIconPixmap( "colorize" ), + tr( "Set random color" ), this, SLOT( randomTrackColor() ) ); + toMenu->addSeparator(); + toMenu->addAction( embed::getIconPixmap( "colorize" ), + tr( "Clear clip colors" ), this, SLOT( useTrackColor() ) ); +} + + +void TrackOperationsWidget::toggleRecording( bool on ) +{ + AutomationTrackView * atv = dynamic_cast( m_trackView ); + if( atv ) + { + for( TrackContentObject * tco : atv->getTrack()->getTCOs() ) + { + AutomationPattern * ap = dynamic_cast( tco ); + if( ap ) { ap->setRecording( on ); } + } + atv->update(); + } +} + + + +void TrackOperationsWidget::recordingOn() +{ + toggleRecording( true ); +} + + +void TrackOperationsWidget::recordingOff() +{ + toggleRecording( false ); +} + From 0cccdd9c46a77b884963d8454a4e87eb063e72f0 Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 26 Nov 2020 19:52:22 -0300 Subject: [PATCH 4/6] Split TrackContentWidget off of Track --- include/Track.h | 110 ---- include/TrackContentWidget.h | 143 +++++ include/TrackView.h | 1 + src/core/Track.cpp | 672 ----------------------- src/gui/CMakeLists.txt | 1 + src/gui/widgets/TrackContentWidget.cpp | 708 +++++++++++++++++++++++++ 6 files changed, 853 insertions(+), 782 deletions(-) create mode 100644 include/TrackContentWidget.h create mode 100644 src/gui/widgets/TrackContentWidget.cpp diff --git a/include/Track.h b/include/Track.h index 364939c862b..305b3b306d4 100644 --- a/include/Track.h +++ b/include/Track.h @@ -27,10 +27,8 @@ #define TRACK_H #include -#include #include #include -#include #include "lmms_basics.h" #include "MidiTime.h" @@ -41,15 +39,10 @@ class QMenu; -class QPushButton; -class PixmapButton; class TextFloat; -class Track; -class TrackContentObjectView; class TrackContainer; class TrackContainerView; -class TrackContentWidget; class TrackView; @@ -248,109 +241,6 @@ protected slots: - -class TrackContentWidget : public QWidget, public JournallingObject -{ - Q_OBJECT - - // qproperties for track background gradients - Q_PROPERTY( QBrush darkerColor READ darkerColor WRITE setDarkerColor ) - Q_PROPERTY( QBrush lighterColor READ lighterColor WRITE setLighterColor ) - Q_PROPERTY( QBrush gridColor READ gridColor WRITE setGridColor ) - Q_PROPERTY( QBrush embossColor READ embossColor WRITE setEmbossColor ) - -public: - TrackContentWidget( TrackView * parent ); - virtual ~TrackContentWidget(); - - /*! \brief Updates the background tile pixmap. */ - void updateBackground(); - - void addTCOView( TrackContentObjectView * tcov ); - void removeTCOView( TrackContentObjectView * tcov ); - void removeTCOView( int tcoNum ) - { - if( tcoNum >= 0 && tcoNum < m_tcoViews.size() ) - { - removeTCOView( m_tcoViews[tcoNum] ); - } - } - - bool canPasteSelection( MidiTime tcoPos, const QDropEvent *de ); - bool canPasteSelection( MidiTime tcoPos, const QMimeData *md, bool allowSameBar = false ); - bool pasteSelection( MidiTime tcoPos, QDropEvent * de ); - bool pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck = false ); - - MidiTime endPosition( const MidiTime & posStart ); - - // qproperty access methods - - QBrush darkerColor() const; - QBrush lighterColor() const; - QBrush gridColor() const; - QBrush embossColor() const; - - void setDarkerColor( const QBrush & c ); - void setLighterColor( const QBrush & c ); - void setGridColor( const QBrush & c ); - void setEmbossColor( const QBrush & c); - -public slots: - void update(); - void changePosition( const MidiTime & newPos = MidiTime( -1 ) ); - -protected: - enum ContextMenuAction - { - Paste - }; - - void contextMenuEvent( QContextMenuEvent * cme ) override; - void contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ); - void dragEnterEvent( QDragEnterEvent * dee ) override; - void dropEvent( QDropEvent * de ) override; - void mousePressEvent( QMouseEvent * me ) override; - void paintEvent( QPaintEvent * pe ) override; - void resizeEvent( QResizeEvent * re ) override; - - QString nodeName() const override - { - return "trackcontentwidget"; - } - - void saveSettings( QDomDocument& doc, QDomElement& element ) override - { - Q_UNUSED(doc) - Q_UNUSED(element) - } - - void loadSettings( const QDomElement& element ) override - { - Q_UNUSED(element) - } - - -private: - Track * getTrack(); - MidiTime getPosition( int mouseX ); - - TrackView * m_trackView; - - typedef QVector tcoViewVector; - tcoViewVector m_tcoViews; - - QPixmap m_background; - - // qproperty fields - QBrush m_darkerColor; - QBrush m_lighterColor; - QBrush m_gridColor; - QBrush m_embossColor; -} ; - - - - // base-class for all tracks class LMMS_EXPORT Track : public Model, public JournallingObject { diff --git a/include/TrackContentWidget.h b/include/TrackContentWidget.h new file mode 100644 index 00000000000..087d89070c9 --- /dev/null +++ b/include/TrackContentWidget.h @@ -0,0 +1,143 @@ +/* + * TrackContentWidget.h - declaration of TrackContentWidget class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef TRACK_CONTENT_WIDGET_H +#define TRACK_CONTENT_WIDGET_H + +#include +#include + +#include "MidiTime.h" +#include "JournallingObject.h" + + +class QMenu; + +class Track; +class TrackContentObjectView; +class TrackView; + + +class TrackContentWidget : public QWidget, public JournallingObject +{ + Q_OBJECT + + // qproperties for track background gradients + Q_PROPERTY( QBrush darkerColor READ darkerColor WRITE setDarkerColor ) + Q_PROPERTY( QBrush lighterColor READ lighterColor WRITE setLighterColor ) + Q_PROPERTY( QBrush gridColor READ gridColor WRITE setGridColor ) + Q_PROPERTY( QBrush embossColor READ embossColor WRITE setEmbossColor ) + +public: + TrackContentWidget( TrackView * parent ); + virtual ~TrackContentWidget(); + + /*! \brief Updates the background tile pixmap. */ + void updateBackground(); + + void addTCOView( TrackContentObjectView * tcov ); + void removeTCOView( TrackContentObjectView * tcov ); + void removeTCOView( int tcoNum ) + { + if( tcoNum >= 0 && tcoNum < m_tcoViews.size() ) + { + removeTCOView( m_tcoViews[tcoNum] ); + } + } + + bool canPasteSelection( MidiTime tcoPos, const QDropEvent *de ); + bool canPasteSelection( MidiTime tcoPos, const QMimeData *md, bool allowSameBar = false ); + bool pasteSelection( MidiTime tcoPos, QDropEvent * de ); + bool pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck = false ); + + MidiTime endPosition( const MidiTime & posStart ); + + // qproperty access methods + + QBrush darkerColor() const; + QBrush lighterColor() const; + QBrush gridColor() const; + QBrush embossColor() const; + + void setDarkerColor( const QBrush & c ); + void setLighterColor( const QBrush & c ); + void setGridColor( const QBrush & c ); + void setEmbossColor( const QBrush & c); + +public slots: + void update(); + void changePosition( const MidiTime & newPos = MidiTime( -1 ) ); + +protected: + enum ContextMenuAction + { + Paste + }; + + void contextMenuEvent( QContextMenuEvent * cme ) override; + void contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ); + void dragEnterEvent( QDragEnterEvent * dee ) override; + void dropEvent( QDropEvent * de ) override; + void mousePressEvent( QMouseEvent * me ) override; + void paintEvent( QPaintEvent * pe ) override; + void resizeEvent( QResizeEvent * re ) override; + + QString nodeName() const override + { + return "trackcontentwidget"; + } + + void saveSettings( QDomDocument& doc, QDomElement& element ) override + { + Q_UNUSED(doc) + Q_UNUSED(element) + } + + void loadSettings( const QDomElement& element ) override + { + Q_UNUSED(element) + } + + +private: + Track * getTrack(); + MidiTime getPosition( int mouseX ); + + TrackView * m_trackView; + + typedef QVector tcoViewVector; + tcoViewVector m_tcoViews; + + QPixmap m_background; + + // qproperty fields + QBrush m_darkerColor; + QBrush m_lighterColor; + QBrush m_gridColor; + QBrush m_embossColor; +} ; + + + +#endif diff --git a/include/TrackView.h b/include/TrackView.h index 1dd3c50c069..4c039dffa57 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -32,6 +32,7 @@ #include "JournallingObject.h" #include "ModelView.h" #include "Track.h" +#include "TrackContentWidget.h" #include "TrackOperationsWidget.h" diff --git a/src/core/Track.cpp b/src/core/Track.cpp index f4e3e8ca693..f2c983bfc24 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -40,7 +40,6 @@ #include #include -#include #include #include #include @@ -49,7 +48,6 @@ #include "AutomationPattern.h" #include "AutomationTrack.h" -#include "BBEditor.h" #include "BBTrack.h" #include "BBTrackContainer.h" #include "ConfigManager.h" @@ -68,10 +66,6 @@ */ const int RESIZE_GRIP_WIDTH = 4; //TrackContentObjectView -/*! Alternate between a darker and a lighter background color every 4 bars - */ -const int BARS_PER_GROUP = 4; //TrackContentWidget - /*! A pointer for that text bubble used when moving segments, etc. * @@ -1271,672 +1265,6 @@ QColor TrackContentObjectView::getColorForDisplay( QColor defaultColor ) - -// =========================================================================== -// trackContentWidget -// =========================================================================== -/*! \brief Create a new trackContentWidget - * - * Creates a new track content widget for the given track. - * The content widget comprises the 'grip bar' and the 'tools' button - * for the track's context menu. - * - * \param parent The parent track. - */ -TrackContentWidget::TrackContentWidget( TrackView * parent ) : - QWidget( parent ), - m_trackView( parent ), - m_darkerColor( Qt::SolidPattern ), - m_lighterColor( Qt::SolidPattern ), - m_gridColor( Qt::SolidPattern ), - m_embossColor( Qt::SolidPattern ) -{ - setAcceptDrops( true ); - - connect( parent->trackContainerView(), - SIGNAL( positionChanged( const MidiTime & ) ), - this, SLOT( changePosition( const MidiTime & ) ) ); - - setStyle( QApplication::style() ); - - updateBackground(); -} - - - - -/*! \brief Destroy this trackContentWidget - * - * Destroys the trackContentWidget. - */ -TrackContentWidget::~TrackContentWidget() -{ -} - - - - -void TrackContentWidget::updateBackground() -{ - const TrackContainerView * tcv = m_trackView->trackContainerView(); - - // Assume even-pixels-per-bar. Makes sense, should be like this anyways - int ppb = static_cast( tcv->pixelsPerBar() ); - - int w = ppb * BARS_PER_GROUP; - int h = height(); - m_background = QPixmap( w * 2, height() ); - QPainter pmp( &m_background ); - - pmp.fillRect( 0, 0, w, h, darkerColor() ); - pmp.fillRect( w, 0, w , h, lighterColor() ); - - // draw lines - // vertical lines - pmp.setPen( QPen( gridColor(), 1 ) ); - for( float x = 0; x < w * 2; x += ppb ) - { - pmp.drawLine( QLineF( x, 0.0, x, h ) ); - } - - pmp.setPen( QPen( embossColor(), 1 ) ); - for( float x = 1.0; x < w * 2; x += ppb ) - { - pmp.drawLine( QLineF( x, 0.0, x, h ) ); - } - - // horizontal line - pmp.setPen( QPen( gridColor(), 1 ) ); - pmp.drawLine( 0, h-1, w*2, h-1 ); - - pmp.end(); - - // Force redraw - update(); -} - - - - -/*! \brief Adds a trackContentObjectView to this widget. - * - * Adds a(nother) trackContentObjectView to our list of views. We also - * check that our position is up-to-date. - * - * \param tcov The trackContentObjectView to add. - */ -void TrackContentWidget::addTCOView( TrackContentObjectView * tcov ) -{ - TrackContentObject * tco = tcov->getTrackContentObject(); - - m_tcoViews.push_back( tcov ); - - tco->saveJournallingState( false ); - changePosition(); - tco->restoreJournallingState(); -} - - - - -/*! \brief Removes the given trackContentObjectView to this widget. - * - * Removes the given trackContentObjectView from our list of views. - * - * \param tcov The trackContentObjectView to add. - */ -void TrackContentWidget::removeTCOView( TrackContentObjectView * tcov ) -{ - tcoViewVector::iterator it = std::find( m_tcoViews.begin(), - m_tcoViews.end(), - tcov ); - if( it != m_tcoViews.end() ) - { - m_tcoViews.erase( it ); - Engine::getSong()->setModified(); - } -} - - - - -/*! \brief Update ourselves by updating all the tCOViews attached. - * - */ -void TrackContentWidget::update() -{ - for( tcoViewVector::iterator it = m_tcoViews.begin(); - it != m_tcoViews.end(); ++it ) - { - ( *it )->setFixedHeight( height() - 1 ); - ( *it )->update(); - } - QWidget::update(); -} - - - - -// resposible for moving track-content-widgets to appropriate position after -// change of visible viewport -/*! \brief Move the trackContentWidget to a new place in time - * - * \param newPos The MIDI time to move to. - */ -void TrackContentWidget::changePosition( const MidiTime & newPos ) -{ - if( m_trackView->trackContainerView() == gui->getBBEditor()->trackContainerView() ) - { - const int curBB = Engine::getBBTrackContainer()->currentBB(); - setUpdatesEnabled( false ); - - // first show TCO for current BB... - for( tcoViewVector::iterator it = m_tcoViews.begin(); - it != m_tcoViews.end(); ++it ) - { - if( ( *it )->getTrackContentObject()-> - startPosition().getBar() == curBB ) - { - ( *it )->move( 0, ( *it )->y() ); - ( *it )->raise(); - ( *it )->show(); - } - else - { - ( *it )->lower(); - } - } - // ...then hide others to avoid flickering - for( tcoViewVector::iterator it = m_tcoViews.begin(); - it != m_tcoViews.end(); ++it ) - { - if( ( *it )->getTrackContentObject()-> - startPosition().getBar() != curBB ) - { - ( *it )->hide(); - } - } - setUpdatesEnabled( true ); - return; - } - - MidiTime pos = newPos; - if( pos < 0 ) - { - pos = m_trackView->trackContainerView()->currentPosition(); - } - - const int begin = pos; - const int end = endPosition( pos ); - const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); - - setUpdatesEnabled( false ); - for( tcoViewVector::iterator it = m_tcoViews.begin(); - it != m_tcoViews.end(); ++it ) - { - TrackContentObjectView * tcov = *it; - TrackContentObject * tco = tcov->getTrackContentObject(); - - tco->changeLength( tco->length() ); - - const int ts = tco->startPosition(); - const int te = tco->endPosition()-3; - if( ( ts >= begin && ts <= end ) || - ( te >= begin && te <= end ) || - ( ts <= begin && te >= end ) ) - { - tcov->move( static_cast( ( ts - begin ) * ppb / - MidiTime::ticksPerBar() ), - tcov->y() ); - if( !tcov->isVisible() ) - { - tcov->show(); - } - } - else - { - tcov->move( -tcov->width()-10, tcov->y() ); - } - } - setUpdatesEnabled( true ); - - // redraw background -// update(); -} - - - - -/*! \brief Return the position of the trackContentWidget in bars. - * - * \param mouseX the mouse's current X position in pixels. - */ -MidiTime TrackContentWidget::getPosition( int mouseX ) -{ - TrackContainerView * tv = m_trackView->trackContainerView(); - return MidiTime( tv->currentPosition() + - mouseX * - MidiTime::ticksPerBar() / - static_cast( tv->pixelsPerBar() ) ); -} - - - - -/*! \brief Respond to a drag enter event on the trackContentWidget - * - * \param dee the Drag Enter Event to respond to - */ -void TrackContentWidget::dragEnterEvent( QDragEnterEvent * dee ) -{ - MidiTime tcoPos = getPosition( dee->pos().x() ); - if( canPasteSelection( tcoPos, dee ) == false ) - { - dee->ignore(); - } - else - { - StringPairDrag::processDragEnterEvent( dee, "tco_" + - QString::number( getTrack()->type() ) ); - } -} - - - - -/*! \brief Returns whether a selection of TCOs can be pasted into this - * - * \param tcoPos the position of the TCO slot being pasted on - * \param de the DropEvent generated - */ -bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* de ) -{ - const QMimeData * mimeData = de->mimeData(); - - // If the source of the DropEvent is the current instance of LMMS we don't allow pasting in the same bar - // if it's another instance of LMMS we allow it - return de->source() - ? canPasteSelection( tcoPos, mimeData ) - : canPasteSelection( tcoPos, mimeData, true ); -} - -// Overloaded method to make it possible to call this method without a Drag&Drop event -bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md , bool allowSameBar ) -{ - // For decodeKey() and decodeValue() - using namespace Clipboard; - - Track * t = getTrack(); - QString type = decodeKey( md ); - QString value = decodeValue( md ); - - // We can only paste into tracks of the same type - if( type != ( "tco_" + QString::number( t->type() ) ) || - m_trackView->trackContainerView()->fixedTCOs() == true ) - { - return false; - } - - // value contains XML needed to reconstruct TCOs and place them - DataFile dataFile( value.toUtf8() ); - - // Extract the metadata and which TCO was grabbed - QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" ); - QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" ); - MidiTime grabbedTCOPos = tcoPosAttr.value().toInt(); - MidiTime grabbedTCOBar = MidiTime( grabbedTCOPos.getBar(), 0 ); - - // Extract the track index that was originally clicked - QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" ); - const int initialTrackIndex = tiAttr.value().toInt(); - - // Get the current track's index - const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); - const int currentTrackIndex = tracks.indexOf( t ); - - // Don't paste if we're on the same bar and allowSameBar is false - auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt(); - if( !allowSameBar && sourceTrackContainerId == t->trackContainer()->id() && - tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) - { - return false; - } - - // Extract the tco data - QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" ); - QDomNodeList tcoNodes = tcoParent.childNodes(); - - // Determine if all the TCOs will land on a valid track - for( int i = 0; i < tcoNodes.length(); i++ ) - { - QDomElement tcoElement = tcoNodes.item( i ).toElement(); - int trackIndex = tcoElement.attributeNode( "trackIndex" ).value().toInt(); - int finalTrackIndex = trackIndex + currentTrackIndex - initialTrackIndex; - - // Track must be in TrackContainer's tracks - if( finalTrackIndex < 0 || finalTrackIndex >= tracks.size() ) - { - return false; - } - - // Track must be of the same type - auto startTrackType = tcoElement.attributeNode("trackType").value().toInt(); - Track * endTrack = tracks.at( finalTrackIndex ); - if( startTrackType != endTrack->type() ) - { - return false; - } - } - - return true; -} - -/*! \brief Pastes a selection of TCOs onto the track - * - * \param tcoPos the position of the TCO slot being pasted on - * \param de the DropEvent generated - */ -bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de ) -{ - const QMimeData * mimeData = de->mimeData(); - - if( canPasteSelection( tcoPos, de ) == false ) - { - return false; - } - - // We set skipSafetyCheck to true because we already called canPasteSelection - return pasteSelection( tcoPos, mimeData, true ); -} - -// Overloaded method so we can call it without a Drag&Drop event -bool TrackContentWidget::pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck ) -{ - // For decodeKey() and decodeValue() - using namespace Clipboard; - - // When canPasteSelection was already called before, skipSafetyCheck will skip this - if( !skipSafetyCheck && canPasteSelection( tcoPos, md ) == false ) - { - return false; - } - - QString type = decodeKey( md ); - QString value = decodeValue( md ); - - getTrack()->addJournalCheckPoint(); - - // value contains XML needed to reconstruct TCOs and place them - DataFile dataFile( value.toUtf8() ); - - // Extract the tco data - QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" ); - QDomNodeList tcoNodes = tcoParent.childNodes(); - - // Extract the track index that was originally clicked - QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" ); - QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" ); - int initialTrackIndex = tiAttr.value().toInt(); - QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" ); - MidiTime grabbedTCOPos = tcoPosAttr.value().toInt(); - - // Snap the mouse position to the beginning of the dropped bar, in ticks - const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks(); - const int currentTrackIndex = tracks.indexOf( getTrack() ); - - bool wasSelection = m_trackView->trackContainerView()->rubberBand()->selectedObjects().count(); - - // Unselect the old group - const QVector so = - m_trackView->trackContainerView()->selectedObjects(); - for( QVector::const_iterator it = so.begin(); - it != so.end(); ++it ) - { - ( *it )->setSelected( false ); - } - - - // TODO -- Need to draw the hovericon either way, or ghost the TCOs - // onto their final position. - - float snapSize = gui->songEditor()->m_editor->getSnapSize(); - // All patterns should be offset the same amount as the grabbed pattern - MidiTime offset = MidiTime(tcoPos - grabbedTCOPos); - // Users expect clips to "fall" backwards, so bias the offset - offset = offset - MidiTime::ticksPerBar() * snapSize / 2; - // The offset is quantized (rather than the positions) to preserve fine adjustments - offset = offset.quantize(snapSize); - - // Get the leftmost TCO and fix the offset if it reaches below bar 0 - MidiTime leftmostPos = grabbedTCOPos; - for(int i = 0; i < tcoNodes.length(); ++i) - { - QDomElement outerTCOElement = tcoNodes.item(i).toElement(); - QDomElement tcoElement = outerTCOElement.firstChildElement(); - - MidiTime pos = tcoElement.attributeNode("pos").value().toInt(); - - if(pos < leftmostPos) { leftmostPos = pos; } - } - // Fix offset if it sets the left most TCO to a negative position - offset = std::max(offset.getTicks(), -leftmostPos.getTicks()); - - for( int i = 0; isongEditor()->m_editor->getSnapSize(); - if (offset == 0) { pos += shift; } - - TrackContentObject * tco = t->createTCO( pos ); - tco->restoreState( tcoElement ); - tco->movePosition(pos); // Because we restored the state, we need to move the TCO again. - if( wasSelection ) - { - tco->selectViewOnCreate( true ); - } - } - - AutomationPattern::resolveAllIDs(); - - return true; -} - - -/*! \brief Respond to a drop event on the trackContentWidget - * - * \param de the Drop Event to respond to - */ -void TrackContentWidget::dropEvent( QDropEvent * de ) -{ - MidiTime tcoPos = MidiTime( getPosition( de->pos().x() ) ); - if( pasteSelection( tcoPos, de ) == true ) - { - de->accept(); - } -} - - - - -/*! \brief Respond to a mouse press on the trackContentWidget - * - * \param me the mouse press event to respond to - */ -void TrackContentWidget::mousePressEvent( QMouseEvent * me ) -{ - if( m_trackView->trackContainerView()->allowRubberband() == true ) - { - QWidget::mousePressEvent( me ); - } - else if( me->modifiers() & Qt::ShiftModifier ) - { - QWidget::mousePressEvent( me ); - } - else if( me->button() == Qt::LeftButton && - !m_trackView->trackContainerView()->fixedTCOs() ) - { - QVector so = m_trackView->trackContainerView()->rubberBand()->selectedObjects(); - for( int i = 0; i < so.count(); ++i ) - { - so.at( i )->setSelected( false); - } - getTrack()->addJournalCheckPoint(); - const MidiTime pos = getPosition( me->x() ).getBar() * - MidiTime::ticksPerBar(); - getTrack()->createTCO(pos); - } -} - - - - -/*! \brief Repaint the trackContentWidget on command - * - * \param pe the Paint Event to respond to - */ -void TrackContentWidget::paintEvent( QPaintEvent * pe ) -{ - // Assume even-pixels-per-bar. Makes sense, should be like this anyways - const TrackContainerView * tcv = m_trackView->trackContainerView(); - int ppb = static_cast( tcv->pixelsPerBar() ); - QPainter p( this ); - // Don't draw background on BB-Editor - if( m_trackView->trackContainerView() != gui->getBBEditor()->trackContainerView() ) - { - p.drawTiledPixmap( rect(), m_background, QPoint( - tcv->currentPosition().getBar() * ppb, 0 ) ); - } -} - - - - -/*! \brief Updates the background tile pixmap on size changes. - * - * \param resizeEvent the resize event to pass to base class - */ -void TrackContentWidget::resizeEvent( QResizeEvent * resizeEvent ) -{ - // Update backgroud - updateBackground(); - // Force redraw - QWidget::resizeEvent( resizeEvent ); -} - - - - -/*! \brief Return the track shown by the trackContentWidget - * - */ -Track * TrackContentWidget::getTrack() -{ - return m_trackView->getTrack(); -} - - - - -/*! \brief Return the end position of the trackContentWidget in Bars. - * - * \param posStart the starting position of the Widget (from getPosition()) - */ -MidiTime TrackContentWidget::endPosition( const MidiTime & posStart ) -{ - const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); - const int w = width(); - return posStart + static_cast( w * MidiTime::ticksPerBar() / ppb ); -} - -void TrackContentWidget::contextMenuEvent( QContextMenuEvent * cme ) -{ - // For hasFormat(), MimeType enum class and getMimeData() - using namespace Clipboard; - - if( cme->modifiers() ) - { - return; - } - - // If we don't have TCO data in the clipboard there's no need to create this menu - // since "paste" is the only action at the moment. - if( ! hasFormat( MimeType::StringPair ) ) - { - return; - } - - QMenu contextMenu( this ); - QAction *pasteA = contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), [this, cme](){ contextMenuAction( cme, Paste ); } ); - // If we can't paste in the current TCW for some reason, disable the action so the user knows - pasteA->setEnabled( canPasteSelection( getPosition( cme->x() ), getMimeData() ) ? true : false ); - - contextMenu.exec( QCursor::pos() ); -} - -void TrackContentWidget::contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ) -{ - // For getMimeData() - using namespace Clipboard; - - switch( action ) - { - case Paste: - // Paste the selection on the MidiTime of the context menu event - MidiTime tcoPos = getPosition( cme->x() ); - - pasteSelection( tcoPos, getMimeData() ); - break; - } -} - - - -// qproperty access methods -//! \brief CSS theming qproperty access method -QBrush TrackContentWidget::darkerColor() const -{ return m_darkerColor; } - -//! \brief CSS theming qproperty access method -QBrush TrackContentWidget::lighterColor() const -{ return m_lighterColor; } - -//! \brief CSS theming qproperty access method -QBrush TrackContentWidget::gridColor() const -{ return m_gridColor; } - -//! \brief CSS theming qproperty access method -QBrush TrackContentWidget::embossColor() const -{ return m_embossColor; } - -//! \brief CSS theming qproperty access method -void TrackContentWidget::setDarkerColor( const QBrush & c ) -{ m_darkerColor = c; } - -//! \brief CSS theming qproperty access method -void TrackContentWidget::setLighterColor( const QBrush & c ) -{ m_lighterColor = c; } - -//! \brief CSS theming qproperty access method -void TrackContentWidget::setGridColor( const QBrush & c ) -{ m_gridColor = c; } - -//! \brief CSS theming qproperty access method -void TrackContentWidget::setEmbossColor( const QBrush & c ) -{ m_embossColor = c; } - - - - // =========================================================================== // track // =========================================================================== diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 40d8f3e2181..4368f192be1 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -98,6 +98,7 @@ SET(LMMS_SRCS gui/widgets/ToolButton.cpp gui/widgets/ToolTip.cpp gui/widgets/TrackLabelButton.cpp + gui/widgets/TrackContentWidget.cpp gui/widgets/TrackOperationsWidget.cpp gui/widgets/TrackRenameLineEdit.cpp gui/widgets/StepRecorderWidget.cpp diff --git a/src/gui/widgets/TrackContentWidget.cpp b/src/gui/widgets/TrackContentWidget.cpp new file mode 100644 index 00000000000..e0182083be5 --- /dev/null +++ b/src/gui/widgets/TrackContentWidget.cpp @@ -0,0 +1,708 @@ +/* + * TrackContentWidget.cpp - implementation of TrackContentWidget class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "TrackContentWidget.h" + +#include +#include +#include +#include + +#include "AutomationPattern.h" +#include "BBEditor.h" +#include "BBTrackContainer.h" +#include "Clipboard.h" +#include "Engine.h" +#include "GuiApplication.h" +#include "Song.h" +#include "SongEditor.h" +#include "StringPairDrag.h" +#include "TrackContainerView.h" +#include "TrackView.h" + + +/*! Alternate between a darker and a lighter background color every 4 bars + */ +const int BARS_PER_GROUP = 4; + + +/*! \brief Create a new trackContentWidget + * + * Creates a new track content widget for the given track. + * The content widget comprises the 'grip bar' and the 'tools' button + * for the track's context menu. + * + * \param parent The parent track. + */ +TrackContentWidget::TrackContentWidget( TrackView * parent ) : + QWidget( parent ), + m_trackView( parent ), + m_darkerColor( Qt::SolidPattern ), + m_lighterColor( Qt::SolidPattern ), + m_gridColor( Qt::SolidPattern ), + m_embossColor( Qt::SolidPattern ) +{ + setAcceptDrops( true ); + + connect( parent->trackContainerView(), + SIGNAL( positionChanged( const MidiTime & ) ), + this, SLOT( changePosition( const MidiTime & ) ) ); + + setStyle( QApplication::style() ); + + updateBackground(); +} + + + + +/*! \brief Destroy this trackContentWidget + * + * Destroys the trackContentWidget. + */ +TrackContentWidget::~TrackContentWidget() +{ +} + + + + +void TrackContentWidget::updateBackground() +{ + const TrackContainerView * tcv = m_trackView->trackContainerView(); + + // Assume even-pixels-per-bar. Makes sense, should be like this anyways + int ppb = static_cast( tcv->pixelsPerBar() ); + + int w = ppb * BARS_PER_GROUP; + int h = height(); + m_background = QPixmap( w * 2, height() ); + QPainter pmp( &m_background ); + + pmp.fillRect( 0, 0, w, h, darkerColor() ); + pmp.fillRect( w, 0, w , h, lighterColor() ); + + // draw lines + // vertical lines + pmp.setPen( QPen( gridColor(), 1 ) ); + for( float x = 0; x < w * 2; x += ppb ) + { + pmp.drawLine( QLineF( x, 0.0, x, h ) ); + } + + pmp.setPen( QPen( embossColor(), 1 ) ); + for( float x = 1.0; x < w * 2; x += ppb ) + { + pmp.drawLine( QLineF( x, 0.0, x, h ) ); + } + + // horizontal line + pmp.setPen( QPen( gridColor(), 1 ) ); + pmp.drawLine( 0, h-1, w*2, h-1 ); + + pmp.end(); + + // Force redraw + update(); +} + + + + +/*! \brief Adds a trackContentObjectView to this widget. + * + * Adds a(nother) trackContentObjectView to our list of views. We also + * check that our position is up-to-date. + * + * \param tcov The trackContentObjectView to add. + */ +void TrackContentWidget::addTCOView( TrackContentObjectView * tcov ) +{ + TrackContentObject * tco = tcov->getTrackContentObject(); + + m_tcoViews.push_back( tcov ); + + tco->saveJournallingState( false ); + changePosition(); + tco->restoreJournallingState(); +} + + + + +/*! \brief Removes the given trackContentObjectView to this widget. + * + * Removes the given trackContentObjectView from our list of views. + * + * \param tcov The trackContentObjectView to add. + */ +void TrackContentWidget::removeTCOView( TrackContentObjectView * tcov ) +{ + tcoViewVector::iterator it = std::find( m_tcoViews.begin(), + m_tcoViews.end(), + tcov ); + if( it != m_tcoViews.end() ) + { + m_tcoViews.erase( it ); + Engine::getSong()->setModified(); + } +} + + + + +/*! \brief Update ourselves by updating all the tCOViews attached. + * + */ +void TrackContentWidget::update() +{ + for( tcoViewVector::iterator it = m_tcoViews.begin(); + it != m_tcoViews.end(); ++it ) + { + ( *it )->setFixedHeight( height() - 1 ); + ( *it )->update(); + } + QWidget::update(); +} + + + + +// resposible for moving track-content-widgets to appropriate position after +// change of visible viewport +/*! \brief Move the trackContentWidget to a new place in time + * + * \param newPos The MIDI time to move to. + */ +void TrackContentWidget::changePosition( const MidiTime & newPos ) +{ + if( m_trackView->trackContainerView() == gui->getBBEditor()->trackContainerView() ) + { + const int curBB = Engine::getBBTrackContainer()->currentBB(); + setUpdatesEnabled( false ); + + // first show TCO for current BB... + for( tcoViewVector::iterator it = m_tcoViews.begin(); + it != m_tcoViews.end(); ++it ) + { + if( ( *it )->getTrackContentObject()-> + startPosition().getBar() == curBB ) + { + ( *it )->move( 0, ( *it )->y() ); + ( *it )->raise(); + ( *it )->show(); + } + else + { + ( *it )->lower(); + } + } + // ...then hide others to avoid flickering + for( tcoViewVector::iterator it = m_tcoViews.begin(); + it != m_tcoViews.end(); ++it ) + { + if( ( *it )->getTrackContentObject()-> + startPosition().getBar() != curBB ) + { + ( *it )->hide(); + } + } + setUpdatesEnabled( true ); + return; + } + + MidiTime pos = newPos; + if( pos < 0 ) + { + pos = m_trackView->trackContainerView()->currentPosition(); + } + + const int begin = pos; + const int end = endPosition( pos ); + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + + setUpdatesEnabled( false ); + for( tcoViewVector::iterator it = m_tcoViews.begin(); + it != m_tcoViews.end(); ++it ) + { + TrackContentObjectView * tcov = *it; + TrackContentObject * tco = tcov->getTrackContentObject(); + + tco->changeLength( tco->length() ); + + const int ts = tco->startPosition(); + const int te = tco->endPosition()-3; + if( ( ts >= begin && ts <= end ) || + ( te >= begin && te <= end ) || + ( ts <= begin && te >= end ) ) + { + tcov->move( static_cast( ( ts - begin ) * ppb / + MidiTime::ticksPerBar() ), + tcov->y() ); + if( !tcov->isVisible() ) + { + tcov->show(); + } + } + else + { + tcov->move( -tcov->width()-10, tcov->y() ); + } + } + setUpdatesEnabled( true ); + + // redraw background +// update(); +} + + + + +/*! \brief Return the position of the trackContentWidget in bars. + * + * \param mouseX the mouse's current X position in pixels. + */ +MidiTime TrackContentWidget::getPosition( int mouseX ) +{ + TrackContainerView * tv = m_trackView->trackContainerView(); + return MidiTime( tv->currentPosition() + + mouseX * + MidiTime::ticksPerBar() / + static_cast( tv->pixelsPerBar() ) ); +} + + + + +/*! \brief Respond to a drag enter event on the trackContentWidget + * + * \param dee the Drag Enter Event to respond to + */ +void TrackContentWidget::dragEnterEvent( QDragEnterEvent * dee ) +{ + MidiTime tcoPos = getPosition( dee->pos().x() ); + if( canPasteSelection( tcoPos, dee ) == false ) + { + dee->ignore(); + } + else + { + StringPairDrag::processDragEnterEvent( dee, "tco_" + + QString::number( getTrack()->type() ) ); + } +} + + + + +/*! \brief Returns whether a selection of TCOs can be pasted into this + * + * \param tcoPos the position of the TCO slot being pasted on + * \param de the DropEvent generated + */ +bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* de ) +{ + const QMimeData * mimeData = de->mimeData(); + + // If the source of the DropEvent is the current instance of LMMS we don't allow pasting in the same bar + // if it's another instance of LMMS we allow it + return de->source() + ? canPasteSelection( tcoPos, mimeData ) + : canPasteSelection( tcoPos, mimeData, true ); +} + +// Overloaded method to make it possible to call this method without a Drag&Drop event +bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QMimeData* md , bool allowSameBar ) +{ + // For decodeKey() and decodeValue() + using namespace Clipboard; + + Track * t = getTrack(); + QString type = decodeKey( md ); + QString value = decodeValue( md ); + + // We can only paste into tracks of the same type + if( type != ( "tco_" + QString::number( t->type() ) ) || + m_trackView->trackContainerView()->fixedTCOs() == true ) + { + return false; + } + + // value contains XML needed to reconstruct TCOs and place them + DataFile dataFile( value.toUtf8() ); + + // Extract the metadata and which TCO was grabbed + QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" ); + QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" ); + MidiTime grabbedTCOPos = tcoPosAttr.value().toInt(); + MidiTime grabbedTCOBar = MidiTime( grabbedTCOPos.getBar(), 0 ); + + // Extract the track index that was originally clicked + QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" ); + const int initialTrackIndex = tiAttr.value().toInt(); + + // Get the current track's index + const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); + const int currentTrackIndex = tracks.indexOf( t ); + + // Don't paste if we're on the same bar and allowSameBar is false + auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt(); + if( !allowSameBar && sourceTrackContainerId == t->trackContainer()->id() && + tcoPos == grabbedTCOBar && currentTrackIndex == initialTrackIndex ) + { + return false; + } + + // Extract the tco data + QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" ); + QDomNodeList tcoNodes = tcoParent.childNodes(); + + // Determine if all the TCOs will land on a valid track + for( int i = 0; i < tcoNodes.length(); i++ ) + { + QDomElement tcoElement = tcoNodes.item( i ).toElement(); + int trackIndex = tcoElement.attributeNode( "trackIndex" ).value().toInt(); + int finalTrackIndex = trackIndex + currentTrackIndex - initialTrackIndex; + + // Track must be in TrackContainer's tracks + if( finalTrackIndex < 0 || finalTrackIndex >= tracks.size() ) + { + return false; + } + + // Track must be of the same type + auto startTrackType = tcoElement.attributeNode("trackType").value().toInt(); + Track * endTrack = tracks.at( finalTrackIndex ); + if( startTrackType != endTrack->type() ) + { + return false; + } + } + + return true; +} + +/*! \brief Pastes a selection of TCOs onto the track + * + * \param tcoPos the position of the TCO slot being pasted on + * \param de the DropEvent generated + */ +bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de ) +{ + const QMimeData * mimeData = de->mimeData(); + + if( canPasteSelection( tcoPos, de ) == false ) + { + return false; + } + + // We set skipSafetyCheck to true because we already called canPasteSelection + return pasteSelection( tcoPos, mimeData, true ); +} + +// Overloaded method so we can call it without a Drag&Drop event +bool TrackContentWidget::pasteSelection( MidiTime tcoPos, const QMimeData * md, bool skipSafetyCheck ) +{ + // For decodeKey() and decodeValue() + using namespace Clipboard; + + // When canPasteSelection was already called before, skipSafetyCheck will skip this + if( !skipSafetyCheck && canPasteSelection( tcoPos, md ) == false ) + { + return false; + } + + QString type = decodeKey( md ); + QString value = decodeValue( md ); + + getTrack()->addJournalCheckPoint(); + + // value contains XML needed to reconstruct TCOs and place them + DataFile dataFile( value.toUtf8() ); + + // Extract the tco data + QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" ); + QDomNodeList tcoNodes = tcoParent.childNodes(); + + // Extract the track index that was originally clicked + QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" ); + QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" ); + int initialTrackIndex = tiAttr.value().toInt(); + QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" ); + MidiTime grabbedTCOPos = tcoPosAttr.value().toInt(); + + // Snap the mouse position to the beginning of the dropped bar, in ticks + const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks(); + const int currentTrackIndex = tracks.indexOf( getTrack() ); + + bool wasSelection = m_trackView->trackContainerView()->rubberBand()->selectedObjects().count(); + + // Unselect the old group + const QVector so = + m_trackView->trackContainerView()->selectedObjects(); + for( QVector::const_iterator it = so.begin(); + it != so.end(); ++it ) + { + ( *it )->setSelected( false ); + } + + + // TODO -- Need to draw the hovericon either way, or ghost the TCOs + // onto their final position. + + float snapSize = gui->songEditor()->m_editor->getSnapSize(); + // All patterns should be offset the same amount as the grabbed pattern + MidiTime offset = MidiTime(tcoPos - grabbedTCOPos); + // Users expect clips to "fall" backwards, so bias the offset + offset = offset - MidiTime::ticksPerBar() * snapSize / 2; + // The offset is quantized (rather than the positions) to preserve fine adjustments + offset = offset.quantize(snapSize); + + // Get the leftmost TCO and fix the offset if it reaches below bar 0 + MidiTime leftmostPos = grabbedTCOPos; + for(int i = 0; i < tcoNodes.length(); ++i) + { + QDomElement outerTCOElement = tcoNodes.item(i).toElement(); + QDomElement tcoElement = outerTCOElement.firstChildElement(); + + MidiTime pos = tcoElement.attributeNode("pos").value().toInt(); + + if(pos < leftmostPos) { leftmostPos = pos; } + } + // Fix offset if it sets the left most TCO to a negative position + offset = std::max(offset.getTicks(), -leftmostPos.getTicks()); + + for( int i = 0; isongEditor()->m_editor->getSnapSize(); + if (offset == 0) { pos += shift; } + + TrackContentObject * tco = t->createTCO( pos ); + tco->restoreState( tcoElement ); + tco->movePosition(pos); // Because we restored the state, we need to move the TCO again. + if( wasSelection ) + { + tco->selectViewOnCreate( true ); + } + } + + AutomationPattern::resolveAllIDs(); + + return true; +} + + +/*! \brief Respond to a drop event on the trackContentWidget + * + * \param de the Drop Event to respond to + */ +void TrackContentWidget::dropEvent( QDropEvent * de ) +{ + MidiTime tcoPos = MidiTime( getPosition( de->pos().x() ) ); + if( pasteSelection( tcoPos, de ) == true ) + { + de->accept(); + } +} + + + + +/*! \brief Respond to a mouse press on the trackContentWidget + * + * \param me the mouse press event to respond to + */ +void TrackContentWidget::mousePressEvent( QMouseEvent * me ) +{ + if( m_trackView->trackContainerView()->allowRubberband() == true ) + { + QWidget::mousePressEvent( me ); + } + else if( me->modifiers() & Qt::ShiftModifier ) + { + QWidget::mousePressEvent( me ); + } + else if( me->button() == Qt::LeftButton && + !m_trackView->trackContainerView()->fixedTCOs() ) + { + QVector so = m_trackView->trackContainerView()->rubberBand()->selectedObjects(); + for( int i = 0; i < so.count(); ++i ) + { + so.at( i )->setSelected( false); + } + getTrack()->addJournalCheckPoint(); + const MidiTime pos = getPosition( me->x() ).getBar() * + MidiTime::ticksPerBar(); + getTrack()->createTCO(pos); + } +} + + + + +/*! \brief Repaint the trackContentWidget on command + * + * \param pe the Paint Event to respond to + */ +void TrackContentWidget::paintEvent( QPaintEvent * pe ) +{ + // Assume even-pixels-per-bar. Makes sense, should be like this anyways + const TrackContainerView * tcv = m_trackView->trackContainerView(); + int ppb = static_cast( tcv->pixelsPerBar() ); + QPainter p( this ); + // Don't draw background on BB-Editor + if( m_trackView->trackContainerView() != gui->getBBEditor()->trackContainerView() ) + { + p.drawTiledPixmap( rect(), m_background, QPoint( + tcv->currentPosition().getBar() * ppb, 0 ) ); + } +} + + + + +/*! \brief Updates the background tile pixmap on size changes. + * + * \param resizeEvent the resize event to pass to base class + */ +void TrackContentWidget::resizeEvent( QResizeEvent * resizeEvent ) +{ + // Update backgroud + updateBackground(); + // Force redraw + QWidget::resizeEvent( resizeEvent ); +} + + + + +/*! \brief Return the track shown by the trackContentWidget + * + */ +Track * TrackContentWidget::getTrack() +{ + return m_trackView->getTrack(); +} + + + + +/*! \brief Return the end position of the trackContentWidget in Bars. + * + * \param posStart the starting position of the Widget (from getPosition()) + */ +MidiTime TrackContentWidget::endPosition( const MidiTime & posStart ) +{ + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + const int w = width(); + return posStart + static_cast( w * MidiTime::ticksPerBar() / ppb ); +} + +void TrackContentWidget::contextMenuEvent( QContextMenuEvent * cme ) +{ + // For hasFormat(), MimeType enum class and getMimeData() + using namespace Clipboard; + + if( cme->modifiers() ) + { + return; + } + + // If we don't have TCO data in the clipboard there's no need to create this menu + // since "paste" is the only action at the moment. + if( ! hasFormat( MimeType::StringPair ) ) + { + return; + } + + QMenu contextMenu( this ); + QAction *pasteA = contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), + tr( "Paste" ), [this, cme](){ contextMenuAction( cme, Paste ); } ); + // If we can't paste in the current TCW for some reason, disable the action so the user knows + pasteA->setEnabled( canPasteSelection( getPosition( cme->x() ), getMimeData() ) ? true : false ); + + contextMenu.exec( QCursor::pos() ); +} + +void TrackContentWidget::contextMenuAction( QContextMenuEvent * cme, ContextMenuAction action ) +{ + // For getMimeData() + using namespace Clipboard; + + switch( action ) + { + case Paste: + // Paste the selection on the MidiTime of the context menu event + MidiTime tcoPos = getPosition( cme->x() ); + + pasteSelection( tcoPos, getMimeData() ); + break; + } +} + + + +// qproperty access methods +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::darkerColor() const +{ return m_darkerColor; } + +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::lighterColor() const +{ return m_lighterColor; } + +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::gridColor() const +{ return m_gridColor; } + +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::embossColor() const +{ return m_embossColor; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setDarkerColor( const QBrush & c ) +{ m_darkerColor = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setLighterColor( const QBrush & c ) +{ m_lighterColor = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setGridColor( const QBrush & c ) +{ m_gridColor = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setEmbossColor( const QBrush & c ) +{ m_embossColor = c; } + From 1cc0bc25feaf3d8efcc9f202c11590afa60bf446 Mon Sep 17 00:00:00 2001 From: M374LX Date: Thu, 26 Nov 2020 22:15:07 -0300 Subject: [PATCH 5/6] Split TrackContentObjectView off of Track --- include/AutomationPattern.h | 1 + include/AutomationPatternView.h | 2 +- include/BBEditor.h | 2 + include/BBTrack.h | 2 +- include/Pattern.h | 8 +- include/SampleTrack.h | 1 + include/Track.h | 186 +---- include/TrackContainerView.h | 1 + include/TrackContentObject.h | 15 +- include/TrackContentObjectView.h | 218 +++++ include/TrackView.h | 3 + src/core/Track.cpp | 1214 +-------------------------- src/gui/CMakeLists.txt | 1 + src/gui/TrackContentObjectView.cpp | 1242 ++++++++++++++++++++++++++++ 14 files changed, 1483 insertions(+), 1413 deletions(-) create mode 100644 include/TrackContentObjectView.h create mode 100644 src/gui/TrackContentObjectView.cpp diff --git a/include/AutomationPattern.h b/include/AutomationPattern.h index cad9d0a1d00..a2b11c98e53 100644 --- a/include/AutomationPattern.h +++ b/include/AutomationPattern.h @@ -31,6 +31,7 @@ #include #include "Track.h" +#include "TrackContentObject.h" class AutomationTrack; diff --git a/include/AutomationPatternView.h b/include/AutomationPatternView.h index a6529b5d4a1..5e7b12977af 100644 --- a/include/AutomationPatternView.h +++ b/include/AutomationPatternView.h @@ -30,7 +30,7 @@ #include "AutomationPattern.h" #include "Song.h" #include "SongEditor.h" -#include "Track.h" +#include "TrackContentObjectView.h" class AutomationPatternView : public TrackContentObjectView diff --git a/include/BBEditor.h b/include/BBEditor.h index 311ed570436..3403e52d317 100644 --- a/include/BBEditor.h +++ b/include/BBEditor.h @@ -26,8 +26,10 @@ #ifndef BB_EDITOR_H #define BB_EDITOR_H + #include "Editor.h" #include "TrackContainerView.h" +#include "TrackContentObjectView.h" class BBTrackContainer; diff --git a/include/BBTrack.h b/include/BBTrack.h index bf4bc9b11d8..7ce4756f6eb 100644 --- a/include/BBTrack.h +++ b/include/BBTrack.h @@ -31,7 +31,7 @@ #include #include -#include "Track.h" +#include "TrackContentObjectView.h" #include "TrackView.h" class TrackLabelButton; diff --git a/include/Pattern.h b/include/Pattern.h index 5192da9faf8..0ee1b09357d 100644 --- a/include/Pattern.h +++ b/include/Pattern.h @@ -34,16 +34,10 @@ #include "Note.h" -#include "Track.h" +#include "TrackContentObjectView.h" -class QAction; -class QProgressBar; -class QPushButton; - class InstrumentTrack; -class SampleBuffer; - class LMMS_EXPORT Pattern : public TrackContentObject diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 819434e0a4e..f9a7c6a3ff0 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -33,6 +33,7 @@ #include "FxMixer.h" #include "FxLineLcdSpinBox.h" #include "Track.h" +#include "TrackContentObjectView.h" #include "TrackView.h" class EffectRackView; diff --git a/include/Track.h b/include/Track.h index 305b3b306d4..8ee9c0b7cc3 100644 --- a/include/Track.h +++ b/include/Track.h @@ -26,23 +26,22 @@ #ifndef TRACK_H #define TRACK_H + #include #include #include +#include "AutomatableModel.h" +#include "DataFile.h" +#include "JournallingObject.h" #include "lmms_basics.h" #include "MidiTime.h" -#include "JournallingObject.h" #include "ModelView.h" -#include "DataFile.h" -#include "TrackContentObject.h" - -class QMenu; -class TextFloat; class TrackContainer; class TrackContainerView; +class TrackContentObject; class TrackView; @@ -66,181 +65,6 @@ const int TCO_BORDER_WIDTH = 2; char const *const FILENAME_FILTER = "[\\0000-\x1f\"*/:<>?\\\\|\x7f]"; -class TrackContentObjectView : public selectableObject, public ModelView -{ - Q_OBJECT - -// theming qproperties - Q_PROPERTY( QColor mutedColor READ mutedColor WRITE setMutedColor ) - Q_PROPERTY( QColor mutedBackgroundColor READ mutedBackgroundColor WRITE setMutedBackgroundColor ) - Q_PROPERTY( QColor selectedColor READ selectedColor WRITE setSelectedColor ) - Q_PROPERTY( QColor textColor READ textColor WRITE setTextColor ) - Q_PROPERTY( QColor textBackgroundColor READ textBackgroundColor WRITE setTextBackgroundColor ) - Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) - Q_PROPERTY( QColor BBPatternBackground READ BBPatternBackground WRITE setBBPatternBackground ) - Q_PROPERTY( bool gradient READ gradient WRITE setGradient ) - // We have to use a QSize here because using QPoint isn't supported. - // width -> x, height -> y - Q_PROPERTY( QSize mouseHotspotHand WRITE setMouseHotspotHand ) - -public: - TrackContentObjectView( TrackContentObject * tco, TrackView * tv ); - virtual ~TrackContentObjectView(); - - bool fixedTCOs(); - - inline TrackContentObject * getTrackContentObject() - { - return m_tco; - } - - inline TrackView * getTrackView() - { - return m_trackView; - } - - // qproperty access func - QColor mutedColor() const; - QColor mutedBackgroundColor() const; - QColor selectedColor() const; - QColor textColor() const; - QColor textBackgroundColor() const; - QColor textShadowColor() const; - QColor BBPatternBackground() const; - bool gradient() const; - void setMutedColor( const QColor & c ); - void setMutedBackgroundColor( const QColor & c ); - void setSelectedColor( const QColor & c ); - void setTextColor( const QColor & c ); - void setTextBackgroundColor( const QColor & c ); - void setTextShadowColor( const QColor & c ); - void setBBPatternBackground( const QColor & c ); - void setGradient( const bool & b ); - void setMouseHotspotHand(const QSize & s); - - // access needsUpdate member variable - bool needsUpdate(); - void setNeedsUpdate( bool b ); - - // Method to get a QVector of TCOs to be affected by a context menu action - QVector getClickedTCOs(); - - // Methods to remove, copy, cut, paste and mute a QVector of TCO views - void copy( QVector tcovs ); - void cut( QVector tcovs ); - void paste(); - // remove and toggleMute are static because they don't depend - // being called from a particular TCO view, but can be called anywhere as long - // as a valid TCO view list is given, while copy/cut require an instance for - // some metadata to be written to the clipboard. - static void remove( QVector tcovs ); - static void toggleMute( QVector tcovs ); - - QColor getColorForDisplay( QColor ); - -public slots: - virtual bool close(); - void remove(); - void update() override; - - void changeClipColor(); - void useTrackColor(); - -protected: - enum ContextMenuAction - { - Remove, - Cut, - Copy, - Paste, - Mute - }; - - virtual void constructContextMenu( QMenu * ) - { - } - - void contextMenuEvent( QContextMenuEvent * cme ) override; - void contextMenuAction( ContextMenuAction action ); - void dragEnterEvent( QDragEnterEvent * dee ) override; - void dropEvent( QDropEvent * de ) override; - void leaveEvent( QEvent * e ) override; - void mousePressEvent( QMouseEvent * me ) override; - void mouseMoveEvent( QMouseEvent * me ) override; - void mouseReleaseEvent( QMouseEvent * me ) override; - void resizeEvent( QResizeEvent * re ) override - { - m_needsUpdate = true; - selectableObject::resizeEvent( re ); - } - - float pixelsPerBar(); - - - DataFile createTCODataFiles(const QVector & tcos) const; - - virtual void paintTextLabel(QString const & text, QPainter & painter); - - -protected slots: - void updateLength(); - void updatePosition(); - - -private: - enum Actions - { - NoAction, - Move, - MoveSelection, - Resize, - ResizeLeft, - CopySelection, - ToggleSelected - } ; - - static TextFloat * s_textFloat; - - TrackContentObject * m_tco; - TrackView * m_trackView; - Actions m_action; - QPoint m_initialMousePos; - QPoint m_initialMouseGlobalPos; - MidiTime m_initialTCOPos; - MidiTime m_initialTCOEnd; - QVector m_initialOffsets; - - TextFloat * m_hint; - -// qproperty fields - QColor m_mutedColor; - QColor m_mutedBackgroundColor; - QColor m_selectedColor; - QColor m_textColor; - QColor m_textBackgroundColor; - QColor m_textShadowColor; - QColor m_BBPatternBackground; - bool m_gradient; - QSize m_mouseHotspotHand; // QSize must be used because QPoint isn't supported by property system - bool m_cursorSetYet; - - bool m_needsUpdate; - inline void setInitialPos( QPoint pos ) - { - m_initialMousePos = pos; - m_initialMouseGlobalPos = mapToGlobal( pos ); - m_initialTCOPos = m_tco->startPosition(); - m_initialTCOEnd = m_initialTCOPos + m_tco->length(); - } - void setInitialOffsets(); - - bool mouseMovedDistance( QMouseEvent * me, int distance ); - MidiTime draggedTCOPos( QMouseEvent * me ); -} ; - - - - // base-class for all tracks class LMMS_EXPORT Track : public Model, public JournallingObject { diff --git a/include/TrackContainerView.h b/include/TrackContainerView.h index 6e952189b01..0116120652f 100644 --- a/include/TrackContainerView.h +++ b/include/TrackContainerView.h @@ -34,6 +34,7 @@ #include "Track.h" #include "JournallingObject.h" #include "InstrumentTrack.h" +#include "Rubberband.h" class QVBoxLayout; diff --git a/include/TrackContentObject.h b/include/TrackContentObject.h index e73c49ba79a..bbd9a999872 100644 --- a/include/TrackContentObject.h +++ b/include/TrackContentObject.h @@ -25,21 +25,16 @@ #ifndef TRACK_CONTENT_OBJECT_H #define TRACK_CONTENT_OBJECT_H -#include -#include -#include -#include #include -#include -#include "lmms_basics.h" -#include "MidiTime.h" -#include "Rubberband.h" -#include "JournallingObject.h" #include "AutomatableModel.h" -#include "ModelView.h" #include "DataFile.h" #include "FadeButton.h" +#include "JournallingObject.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Model.h" +#include "Rubberband.h" class QMenu; diff --git a/include/TrackContentObjectView.h b/include/TrackContentObjectView.h new file mode 100644 index 00000000000..e35119210ef --- /dev/null +++ b/include/TrackContentObjectView.h @@ -0,0 +1,218 @@ +/* + * TrackContentObjectView.h - declaration of TrackContentObjectView class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef TRACK_CONTENT_OBJECT_VIEW_H +#define TRACK_CONTENT_OBJECT_VIEW_H + + +#include + +#include "ModelView.h" +#include "Rubberband.h" +#include "TrackContentObject.h" + + +class QMenu; +class QContextMenuEvent; + +class DataFile; +class TextFloat; +class TrackContentObject; +class TrackView; + + +class TrackContentObjectView : public selectableObject, public ModelView +{ + Q_OBJECT + +// theming qproperties + Q_PROPERTY( QColor mutedColor READ mutedColor WRITE setMutedColor ) + Q_PROPERTY( QColor mutedBackgroundColor READ mutedBackgroundColor WRITE setMutedBackgroundColor ) + Q_PROPERTY( QColor selectedColor READ selectedColor WRITE setSelectedColor ) + Q_PROPERTY( QColor textColor READ textColor WRITE setTextColor ) + Q_PROPERTY( QColor textBackgroundColor READ textBackgroundColor WRITE setTextBackgroundColor ) + Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) + Q_PROPERTY( QColor BBPatternBackground READ BBPatternBackground WRITE setBBPatternBackground ) + Q_PROPERTY( bool gradient READ gradient WRITE setGradient ) + // We have to use a QSize here because using QPoint isn't supported. + // width -> x, height -> y + Q_PROPERTY( QSize mouseHotspotHand WRITE setMouseHotspotHand ) + +public: + TrackContentObjectView( TrackContentObject * tco, TrackView * tv ); + virtual ~TrackContentObjectView(); + + bool fixedTCOs(); + + inline TrackContentObject * getTrackContentObject() + { + return m_tco; + } + + inline TrackView * getTrackView() + { + return m_trackView; + } + + // qproperty access func + QColor mutedColor() const; + QColor mutedBackgroundColor() const; + QColor selectedColor() const; + QColor textColor() const; + QColor textBackgroundColor() const; + QColor textShadowColor() const; + QColor BBPatternBackground() const; + bool gradient() const; + void setMutedColor( const QColor & c ); + void setMutedBackgroundColor( const QColor & c ); + void setSelectedColor( const QColor & c ); + void setTextColor( const QColor & c ); + void setTextBackgroundColor( const QColor & c ); + void setTextShadowColor( const QColor & c ); + void setBBPatternBackground( const QColor & c ); + void setGradient( const bool & b ); + void setMouseHotspotHand(const QSize & s); + + // access needsUpdate member variable + bool needsUpdate(); + void setNeedsUpdate( bool b ); + + // Method to get a QVector of TCOs to be affected by a context menu action + QVector getClickedTCOs(); + + // Methods to remove, copy, cut, paste and mute a QVector of TCO views + void copy( QVector tcovs ); + void cut( QVector tcovs ); + void paste(); + // remove and toggleMute are static because they don't depend + // being called from a particular TCO view, but can be called anywhere as long + // as a valid TCO view list is given, while copy/cut require an instance for + // some metadata to be written to the clipboard. + static void remove( QVector tcovs ); + static void toggleMute( QVector tcovs ); + + QColor getColorForDisplay( QColor ); + +public slots: + virtual bool close(); + void remove(); + void update() override; + + void changeClipColor(); + void useTrackColor(); + +protected: + enum ContextMenuAction + { + Remove, + Cut, + Copy, + Paste, + Mute + }; + + virtual void constructContextMenu( QMenu * ) + { + } + + void contextMenuEvent( QContextMenuEvent * cme ) override; + void contextMenuAction( ContextMenuAction action ); + void dragEnterEvent( QDragEnterEvent * dee ) override; + void dropEvent( QDropEvent * de ) override; + void leaveEvent( QEvent * e ) override; + void mousePressEvent( QMouseEvent * me ) override; + void mouseMoveEvent( QMouseEvent * me ) override; + void mouseReleaseEvent( QMouseEvent * me ) override; + void resizeEvent( QResizeEvent * re ) override + { + m_needsUpdate = true; + selectableObject::resizeEvent( re ); + } + + float pixelsPerBar(); + + + DataFile createTCODataFiles(const QVector & tcos) const; + + virtual void paintTextLabel(QString const & text, QPainter & painter); + + +protected slots: + void updateLength(); + void updatePosition(); + + +private: + enum Actions + { + NoAction, + Move, + MoveSelection, + Resize, + ResizeLeft, + CopySelection, + ToggleSelected + } ; + + static TextFloat * s_textFloat; + + TrackContentObject * m_tco; + TrackView * m_trackView; + Actions m_action; + QPoint m_initialMousePos; + QPoint m_initialMouseGlobalPos; + MidiTime m_initialTCOPos; + MidiTime m_initialTCOEnd; + QVector m_initialOffsets; + + TextFloat * m_hint; + +// qproperty fields + QColor m_mutedColor; + QColor m_mutedBackgroundColor; + QColor m_selectedColor; + QColor m_textColor; + QColor m_textBackgroundColor; + QColor m_textShadowColor; + QColor m_BBPatternBackground; + bool m_gradient; + QSize m_mouseHotspotHand; // QSize must be used because QPoint isn't supported by property system + bool m_cursorSetYet; + + bool m_needsUpdate; + inline void setInitialPos( QPoint pos ) + { + m_initialMousePos = pos; + m_initialMouseGlobalPos = mapToGlobal( pos ); + m_initialTCOPos = m_tco->startPosition(); + m_initialTCOEnd = m_initialTCOPos + m_tco->length(); + } + void setInitialOffsets(); + + bool mouseMovedDistance( QMouseEvent * me, int distance ); + MidiTime draggedTCOPos( QMouseEvent * me ); +} ; + + +#endif diff --git a/include/TrackView.h b/include/TrackView.h index 4c039dffa57..15384219dff 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -36,6 +36,9 @@ #include "TrackOperationsWidget.h" +class FadeButton; + + class TrackView : public QWidget, public ModelView, public JournallingObject { Q_OBJECT diff --git a/src/core/Track.cpp b/src/core/Track.cpp index f2c983bfc24..7c47b620075 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -51,1224 +51,12 @@ #include "BBTrack.h" #include "BBTrackContainer.h" #include "ConfigManager.h" -#include "Clipboard.h" -#include "ColorChooser.h" #include "Engine.h" -#include "GuiApplication.h" +#include "InstrumentTrack.h" #include "SampleTrack.h" #include "Song.h" -#include "SongEditor.h" -#include "StringPairDrag.h" -#include "TextFloat.h" -/*! The width of the resize grip in pixels - */ -const int RESIZE_GRIP_WIDTH = 4; //TrackContentObjectView - - -/*! A pointer for that text bubble used when moving segments, etc. - * - * In a number of situations, LMMS displays a floating text bubble - * beside the cursor as you move or resize elements of a track about. - * This pointer keeps track of it, as you only ever need one at a time. - */ -TextFloat * TrackContentObjectView::s_textFloat = NULL; - - -// =========================================================================== -// trackContentObjectView -// =========================================================================== -/*! \brief Create a new trackContentObjectView - * - * Creates a new track content object view for the given - * track content object in the given track view. - * - * \param _tco The track content object to be displayed - * \param _tv The track view that will contain the new object - */ -TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco, - TrackView * tv ) : - selectableObject( tv->getTrackContentWidget() ), - ModelView( NULL, this ), - m_tco( tco ), - m_trackView( tv ), - m_action( NoAction ), - m_initialMousePos( QPoint( 0, 0 ) ), - m_initialMouseGlobalPos( QPoint( 0, 0 ) ), - m_initialTCOPos( MidiTime(0) ), - m_initialTCOEnd( MidiTime(0) ), - m_initialOffsets( QVector() ), - m_hint( NULL ), - m_mutedColor( 0, 0, 0 ), - m_mutedBackgroundColor( 0, 0, 0 ), - m_selectedColor( 0, 0, 0 ), - m_textColor( 0, 0, 0 ), - m_textShadowColor( 0, 0, 0 ), - m_BBPatternBackground( 0, 0, 0 ), - m_gradient( true ), - m_mouseHotspotHand( 0, 0 ), - m_cursorSetYet( false ), - m_needsUpdate( true ) -{ - if( s_textFloat == NULL ) - { - s_textFloat = new TextFloat; - s_textFloat->setPixmap( embed::getIconPixmap( "clock" ) ); - } - - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setAttribute( Qt::WA_DeleteOnClose, true ); - setFocusPolicy( Qt::StrongFocus ); - setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); - move( 0, 0 ); - show(); - - setFixedHeight( tv->getTrackContentWidget()->height() - 1); - setAcceptDrops( true ); - setMouseTracking( true ); - - connect( m_tco, SIGNAL( lengthChanged() ), - this, SLOT( updateLength() ) ); - connect( gui->songEditor()->m_editor->zoomingModel(), SIGNAL( dataChanged() ), this, SLOT( updateLength() ) ); - connect( m_tco, SIGNAL( positionChanged() ), - this, SLOT( updatePosition() ) ); - connect( m_tco, SIGNAL( destroyedTCO() ), this, SLOT( close() ) ); - setModel( m_tco ); - connect( m_tco, SIGNAL( trackColorChanged() ), this, SLOT( update() ) ); - connect( m_trackView->getTrackOperationsWidget(), SIGNAL( colorParented() ), this, SLOT( useTrackColor() ) ); - - m_trackView->getTrackContentWidget()->addTCOView( this ); - updateLength(); - updatePosition(); -} - - - - -/*! \brief Destroy a trackContentObjectView - * - * Destroys the given track content object view. - * - */ -TrackContentObjectView::~TrackContentObjectView() -{ - delete m_hint; - // we have to give our track-container the focus because otherwise the - // op-buttons of our track-widgets could become focus and when the user - // presses space for playing song, just one of these buttons is pressed - // which results in unwanted effects - m_trackView->trackContainerView()->setFocus(); -} - - -/*! \brief Update a TrackContentObjectView - * - * TCO's get drawn only when needed, - * and when a TCO is updated, - * it needs to be redrawn. - * - */ -void TrackContentObjectView::update() -{ - if( !m_cursorSetYet ) - { - setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); - m_cursorSetYet = true; - } - - if( fixedTCOs() ) - { - updateLength(); - } - m_needsUpdate = true; - selectableObject::update(); -} - - - -/*! \brief Does this trackContentObjectView have a fixed TCO? - * - * Returns whether the containing trackView has fixed - * TCOs. - * - * \todo What the hell is a TCO here - track content object? And in - * what circumstance are they fixed? - */ -bool TrackContentObjectView::fixedTCOs() -{ - return m_trackView->trackContainerView()->fixedTCOs(); -} - - - -// qproperty access functions, to be inherited & used by TCOviews -//! \brief CSS theming qproperty access method -QColor TrackContentObjectView::mutedColor() const -{ return m_mutedColor; } - -QColor TrackContentObjectView::mutedBackgroundColor() const -{ return m_mutedBackgroundColor; } - -QColor TrackContentObjectView::selectedColor() const -{ return m_selectedColor; } - -QColor TrackContentObjectView::textColor() const -{ return m_textColor; } - -QColor TrackContentObjectView::textBackgroundColor() const -{ - return m_textBackgroundColor; -} - -QColor TrackContentObjectView::textShadowColor() const -{ return m_textShadowColor; } - -QColor TrackContentObjectView::BBPatternBackground() const -{ return m_BBPatternBackground; } - -bool TrackContentObjectView::gradient() const -{ return m_gradient; } - -//! \brief CSS theming qproperty access method -void TrackContentObjectView::setMutedColor( const QColor & c ) -{ m_mutedColor = QColor( c ); } - -void TrackContentObjectView::setMutedBackgroundColor( const QColor & c ) -{ m_mutedBackgroundColor = QColor( c ); } - -void TrackContentObjectView::setSelectedColor( const QColor & c ) -{ m_selectedColor = QColor( c ); } - -void TrackContentObjectView::setTextColor( const QColor & c ) -{ m_textColor = QColor( c ); } - -void TrackContentObjectView::setTextBackgroundColor( const QColor & c ) -{ - m_textBackgroundColor = c; -} - -void TrackContentObjectView::setTextShadowColor( const QColor & c ) -{ m_textShadowColor = QColor( c ); } - -void TrackContentObjectView::setBBPatternBackground( const QColor & c ) -{ m_BBPatternBackground = QColor( c ); } - -void TrackContentObjectView::setGradient( const bool & b ) -{ m_gradient = b; } - -void TrackContentObjectView::setMouseHotspotHand(const QSize & s) -{ - m_mouseHotspotHand = s; -} - -// access needsUpdate member variable -bool TrackContentObjectView::needsUpdate() -{ return m_needsUpdate; } -void TrackContentObjectView::setNeedsUpdate( bool b ) -{ m_needsUpdate = b; } - -/*! \brief Close a trackContentObjectView - * - * Closes a track content object view by asking the track - * view to remove us and then asking the QWidget to close us. - * - * \return Boolean state of whether the QWidget was able to close. - */ -bool TrackContentObjectView::close() -{ - m_trackView->getTrackContentWidget()->removeTCOView( this ); - return QWidget::close(); -} - - - - -/*! \brief Removes a trackContentObjectView from its track view. - * - * Like the close() method, this asks the track view to remove this - * track content object view. However, the track content object is - * scheduled for later deletion rather than closed immediately. - * - */ -void TrackContentObjectView::remove() -{ - m_trackView->getTrack()->addJournalCheckPoint(); - - // delete ourself - close(); - m_tco->deleteLater(); -} - - - - -/*! \brief Updates a trackContentObjectView's length - * - * If this track content object view has a fixed TCO, then we must - * keep the width of our parent. Otherwise, calculate our width from - * the track content object's length in pixels adding in the border. - * - */ -void TrackContentObjectView::updateLength() -{ - if( fixedTCOs() ) - { - setFixedWidth( parentWidget()->width() ); - } - else - { - setFixedWidth( - static_cast( m_tco->length() * pixelsPerBar() / - MidiTime::ticksPerBar() ) + 1 /*+ - TCO_BORDER_WIDTH * 2-1*/ ); - } - m_trackView->trackContainerView()->update(); -} - - - - -/*! \brief Updates a trackContentObjectView's position. - * - * Ask our track view to change our position. Then make sure that the - * track view is updated in case this position has changed the track - * view's length. - * - */ -void TrackContentObjectView::updatePosition() -{ - m_trackView->getTrackContentWidget()->changePosition(); - // moving a TCO can result in change of song-length etc., - // therefore we update the track-container - m_trackView->trackContainerView()->update(); -} - - - - -void TrackContentObjectView::changeClipColor() -{ - // Get a color from the user - QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )->getColor( m_tco->color() ); - if( ! new_color.isValid() ) - { return; } - - // Use that color - m_tco->setColor( new_color ); - m_tco->useCustomClipColor( true ); - update(); -} - - - -void TrackContentObjectView::useTrackColor() -{ - m_tco->useCustomClipColor( false ); - update(); -} - - - - - -/*! \brief Change the trackContentObjectView's display when something - * being dragged enters it. - * - * We need to notify Qt to change our display if something being - * dragged has entered our 'airspace'. - * - * \param dee The QDragEnterEvent to watch. - */ -void TrackContentObjectView::dragEnterEvent( QDragEnterEvent * dee ) -{ - TrackContentWidget * tcw = getTrackView()->getTrackContentWidget(); - MidiTime tcoPos = MidiTime( m_tco->startPosition() ); - - if( tcw->canPasteSelection( tcoPos, dee ) == false ) - { - dee->ignore(); - } - else - { - StringPairDrag::processDragEnterEvent( dee, "tco_" + - QString::number( m_tco->getTrack()->type() ) ); - } -} - - - - -/*! \brief Handle something being dropped on this trackContentObjectView. - * - * When something has been dropped on this trackContentObjectView, and - * it's a track content object, then use an instance of our dataFile reader - * to take the xml of the track content object and turn it into something - * we can write over our current state. - * - * \param de The QDropEvent to handle. - */ -void TrackContentObjectView::dropEvent( QDropEvent * de ) -{ - QString type = StringPairDrag::decodeKey( de ); - QString value = StringPairDrag::decodeValue( de ); - - // Track must be the same type to paste into - if( type != ( "tco_" + QString::number( m_tco->getTrack()->type() ) ) ) - { - return; - } - - // Defer to rubberband paste if we're in that mode - if( m_trackView->trackContainerView()->allowRubberband() == true ) - { - TrackContentWidget * tcw = getTrackView()->getTrackContentWidget(); - MidiTime tcoPos = MidiTime( m_tco->startPosition() ); - - if( tcw->pasteSelection( tcoPos, de ) == true ) - { - de->accept(); - } - return; - } - - // Don't allow pasting a tco into itself. - QObject* qwSource = de->source(); - if( qwSource != NULL && - dynamic_cast( qwSource ) == this ) - { - return; - } - - // Copy state into existing tco - DataFile dataFile( value.toUtf8() ); - MidiTime pos = m_tco->startPosition(); - QDomElement tcos = dataFile.content().firstChildElement( "tcos" ); - m_tco->restoreState( tcos.firstChildElement().firstChildElement() ); - m_tco->movePosition( pos ); - AutomationPattern::resolveAllIDs(); - de->accept(); -} - - - - -/*! \brief Handle a dragged selection leaving our 'airspace'. - * - * \param e The QEvent to watch. - */ -void TrackContentObjectView::leaveEvent( QEvent * e ) -{ - if( cursor().shape() != Qt::BitmapCursor ) - { - setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); - } - if( e != NULL ) - { - QWidget::leaveEvent( e ); - } -} - -/*! \brief Create a DataFile suitable for copying multiple trackContentObjects. - * - * trackContentObjects in the vector are written to the "tcos" node in the - * DataFile. The trackContentObjectView's initial mouse position is written - * to the "initialMouseX" node in the DataFile. When dropped on a track, - * this is used to create copies of the TCOs. - * - * \param tcos The trackContectObjects to save in a DataFile - */ -DataFile TrackContentObjectView::createTCODataFiles( - const QVector & tcoViews) const -{ - Track * t = m_trackView->getTrack(); - TrackContainer * tc = t->trackContainer(); - DataFile dataFile( DataFile::DragNDropData ); - QDomElement tcoParent = dataFile.createElement( "tcos" ); - - typedef QVector tcoViewVector; - for( tcoViewVector::const_iterator it = tcoViews.begin(); - it != tcoViews.end(); ++it ) - { - // Insert into the dom under the "tcos" element - Track* tcoTrack = ( *it )->m_trackView->getTrack(); - int trackIndex = tc->tracks().indexOf( tcoTrack ); - QDomElement tcoElement = dataFile.createElement( "tco" ); - tcoElement.setAttribute( "trackIndex", trackIndex ); - tcoElement.setAttribute( "trackType", tcoTrack->type() ); - tcoElement.setAttribute( "trackName", tcoTrack->name() ); - ( *it )->m_tco->saveState( dataFile, tcoElement ); - tcoParent.appendChild( tcoElement ); - } - - dataFile.content().appendChild( tcoParent ); - - // Add extra metadata needed for calculations later - int initialTrackIndex = tc->tracks().indexOf( t ); - if( initialTrackIndex < 0 ) - { - printf("Failed to find selected track in the TrackContainer.\n"); - return dataFile; - } - QDomElement metadata = dataFile.createElement( "copyMetadata" ); - // initialTrackIndex is the index of the track that was touched - metadata.setAttribute( "initialTrackIndex", initialTrackIndex ); - metadata.setAttribute( "trackContainerId", tc->id() ); - // grabbedTCOPos is the pos of the bar containing the TCO we grabbed - metadata.setAttribute( "grabbedTCOPos", m_tco->startPosition() ); - - dataFile.content().appendChild( metadata ); - - return dataFile; -} - -void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & painter) -{ - if (text.trimmed() == "") - { - return; - } - - painter.setRenderHint( QPainter::TextAntialiasing ); - - QFont labelFont = this->font(); - labelFont.setHintingPreference( QFont::PreferFullHinting ); - painter.setFont( labelFont ); - - const int textTop = TCO_BORDER_WIDTH + 1; - const int textLeft = TCO_BORDER_WIDTH + 3; - - QFontMetrics fontMetrics(labelFont); - QString elidedPatternName = fontMetrics.elidedText(text, Qt::ElideMiddle, width() - 2 * textLeft); - - if (elidedPatternName.length() < 2) - { - elidedPatternName = text.trimmed(); - } - - painter.fillRect(QRect(0, 0, width(), fontMetrics.height() + 2 * textTop), textBackgroundColor()); - - int const finalTextTop = textTop + fontMetrics.ascent(); - painter.setPen(textShadowColor()); - painter.drawText( textLeft + 1, finalTextTop + 1, elidedPatternName ); - painter.setPen( textColor() ); - painter.drawText( textLeft, finalTextTop, elidedPatternName ); -} - -/*! \brief Handle a mouse press on this trackContentObjectView. - * - * Handles the various ways in which a trackContentObjectView can be - * used with a click of a mouse button. - * - * * If our container supports rubber band selection then handle - * selection events. - * * or if shift-left button, add this object to the selection - * * or if ctrl-left button, start a drag-copy event - * * or if just plain left button, resize if we're resizeable - * * or if ctrl-middle button, mute the track content object - * * or if middle button, maybe delete the track content object. - * - * \param me The QMouseEvent to handle. - */ -void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) -{ - // Right now, active is only used on right/mid clicks actions, so we use a ternary operator - // to avoid the overhead of calling getClickedTCOs when it's not used - auto active = me->button() == Qt::LeftButton - ? QVector() - : getClickedTCOs(); - - setInitialPos( me->pos() ); - setInitialOffsets(); - if( !fixedTCOs() && me->button() == Qt::LeftButton ) - { - if( me->modifiers() & Qt::ControlModifier ) - { - if( isSelected() ) - { - m_action = CopySelection; - } - else - { - m_action = ToggleSelected; - } - } - else if( !me->modifiers() - || (me->modifiers() & Qt::AltModifier) - || (me->modifiers() & Qt::ShiftModifier) ) - { - if( isSelected() ) - { - m_action = MoveSelection; - } - else - { - gui->songEditor()->m_editor->selectAllTcos( false ); - m_tco->addJournalCheckPoint(); - - // move or resize - m_tco->setJournalling( false ); - - setInitialPos( me->pos() ); - setInitialOffsets(); - - SampleTCO * sTco = dynamic_cast( m_tco ); - if( me->x() < RESIZE_GRIP_WIDTH && sTco - && !m_tco->getAutoResize() ) - { - m_action = ResizeLeft; - setCursor( Qt::SizeHorCursor ); - } - else if( m_tco->getAutoResize() || me->x() < width() - RESIZE_GRIP_WIDTH ) - { - m_action = Move; - setCursor( Qt::SizeAllCursor ); - } - else - { - m_action = Resize; - setCursor( Qt::SizeHorCursor ); - } - - if( m_action == Move ) - { - s_textFloat->setTitle( tr( "Current position" ) ); - s_textFloat->setText( QString( "%1:%2" ). - arg( m_tco->startPosition().getBar() + 1 ). - arg( m_tco->startPosition().getTicks() % - MidiTime::ticksPerBar() ) ); - } - else if( m_action == Resize || m_action == ResizeLeft ) - { - s_textFloat->setTitle( tr( "Current length" ) ); - s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). - arg( m_tco->length().getBar() ). - arg( m_tco->length().getTicks() % - MidiTime::ticksPerBar() ). - arg( m_tco->startPosition().getBar() + 1 ). - arg( m_tco->startPosition().getTicks() % - MidiTime::ticksPerBar() ). - arg( m_tco->endPosition().getBar() + 1 ). - arg( m_tco->endPosition().getTicks() % - MidiTime::ticksPerBar() ) ); - } - // s_textFloat->reparent( this ); - // setup text-float as if TCO was already moved/resized - s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); - s_textFloat->show(); - } - - delete m_hint; - QString hint = m_action == Move || m_action == MoveSelection - ? tr( "Press <%1> and drag to make a copy." ) - : tr( "Press <%1> for free resizing." ); - m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_CTRL_KEY), - embed::getIconPixmap( "hint" ), 0 ); - } - } - else if( me->button() == Qt::RightButton ) - { - if( me->modifiers() & Qt::ControlModifier ) - { - toggleMute( active ); - } - else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) - { - remove( active ); - } - } - else if( me->button() == Qt::MidButton ) - { - if( me->modifiers() & Qt::ControlModifier ) - { - toggleMute( active ); - } - else if( !fixedTCOs() ) - { - remove( active ); - } - } -} - - - - -/*! \brief Handle a mouse movement (drag) on this trackContentObjectView. - * - * Handles the various ways in which a trackContentObjectView can be - * used with a mouse drag. - * - * * If in move mode, move ourselves in the track, - * * or if in move-selection mode, move the entire selection, - * * or if in resize mode, resize ourselves, - * * otherwise ??? - * - * \param me The QMouseEvent to handle. - * \todo what does the final else case do here? - */ -void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) -{ - if( m_action == CopySelection || m_action == ToggleSelected ) - { - if( mouseMovedDistance( me, 2 ) == true ) - { - QVector tcoViews; - if( m_action == CopySelection ) - { - // Collect all selected TCOs - QVector so = - m_trackView->trackContainerView()->selectedObjects(); - for( auto it = so.begin(); it != so.end(); ++it ) - { - TrackContentObjectView * tcov = - dynamic_cast( *it ); - if( tcov != NULL ) - { - tcoViews.push_back( tcov ); - } - } - } - else - { - gui->songEditor()->m_editor->selectAllTcos( false ); - tcoViews.push_back( this ); - } - // Clear the action here because mouseReleaseEvent will not get - // triggered once we go into drag. - m_action = NoAction; - - // Write the TCOs to the DataFile for copying - DataFile dataFile = createTCODataFiles( tcoViews ); - - // TODO -- thumbnail for all selected - QPixmap thumbnail = grab().scaled( - 128, 128, - Qt::KeepAspectRatio, - Qt::SmoothTransformation ); - new StringPairDrag( QString( "tco_%1" ).arg( - m_tco->getTrack()->type() ), - dataFile.toString(), thumbnail, this ); - } - } - - if( me->modifiers() & Qt::ControlModifier ) - { - delete m_hint; - m_hint = NULL; - } - - const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); - if( m_action == Move ) - { - MidiTime newPos = draggedTCOPos( me ); - - m_tco->movePosition(newPos); - newPos = m_tco->startPosition(); // Get the real position the TCO was dragged to for the label - m_trackView->getTrackContentWidget()->changePosition(); - s_textFloat->setText( QString( "%1:%2" ). - arg( newPos.getBar() + 1 ). - arg( newPos.getTicks() % - MidiTime::ticksPerBar() ) ); - s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) ); - } - else if( m_action == MoveSelection ) - { - // 1: Find the position we want to move the grabbed TCO to - MidiTime newPos = draggedTCOPos( me ); - - // 2: Handle moving the other selected TCOs the same distance - QVector so = - m_trackView->trackContainerView()->selectedObjects(); - QVector tcos; // List of selected clips - int leftmost = 0; // Leftmost clip's offset from grabbed clip - // Populate tcos, find leftmost - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView * tcov = - dynamic_cast( *it ); - if( tcov == NULL ) { continue; } - tcos.push_back( tcov->m_tco ); - int index = std::distance( so.begin(), it ); - leftmost = min (leftmost, m_initialOffsets[index].getTicks() ); - } - // Make sure the leftmost clip doesn't get moved to a negative position - if ( newPos.getTicks() + leftmost < 0 ) { newPos = -leftmost; } - - for( QVector::iterator it = tcos.begin(); - it != tcos.end(); ++it ) - { - int index = std::distance( tcos.begin(), it ); - ( *it )->movePosition( newPos + m_initialOffsets[index] ); - } - } - else if( m_action == Resize || m_action == ResizeLeft ) - { - // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize - const bool unquantized = (me->modifiers() & Qt::ControlModifier) || (me->modifiers() & Qt::AltModifier); - const float snapSize = gui->songEditor()->m_editor->getSnapSize(); - // Length in ticks of one snap increment - const MidiTime snapLength = MidiTime( (int)(snapSize * MidiTime::ticksPerBar()) ); - - if( m_action == Resize ) - { - // The clip's new length - MidiTime l = static_cast( me->x() * MidiTime::ticksPerBar() / ppb ); - - if ( unquantized ) - { // We want to preserve this adjusted offset, - // even if the user switches to snapping later - setInitialPos( m_initialMousePos ); - // Don't resize to less than 1 tick - m_tco->changeLength( qMax( 1, l ) ); - } - else if ( me->modifiers() & Qt::ShiftModifier ) - { // If shift is held, quantize clip's end position - MidiTime end = MidiTime( m_initialTCOPos + l ).quantize( snapSize ); - // The end position has to be after the clip's start - MidiTime min = m_initialTCOPos.quantize( snapSize ); - if ( min <= m_initialTCOPos ) min += snapLength; - m_tco->changeLength( qMax(min - m_initialTCOPos, end - m_initialTCOPos) ); - } - else - { // Otherwise, resize in fixed increments - MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos; - MidiTime offset = MidiTime( l - initialLength ).quantize( snapSize ); - // Don't resize to less than 1 tick - MidiTime min = MidiTime( initialLength % snapLength ); - if (min < 1) min += snapLength; - m_tco->changeLength( qMax( min, initialLength + offset) ); - } - } - else - { - SampleTCO * sTco = dynamic_cast( m_tco ); - if( sTco ) - { - const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x(); - - MidiTime t = qMax( 0, (int) - m_trackView->trackContainerView()->currentPosition() + - static_cast( x * MidiTime::ticksPerBar() / ppb ) ); - - if( unquantized ) - { // We want to preserve this adjusted offset, - // even if the user switches to snapping later - setInitialPos( m_initialMousePos ); - //Don't resize to less than 1 tick - t = qMin( m_initialTCOEnd - 1, t); - } - else if( me->modifiers() & Qt::ShiftModifier ) - { // If shift is held, quantize clip's start position - // Don't let the start position move past the end position - MidiTime max = m_initialTCOEnd.quantize( snapSize ); - if ( max >= m_initialTCOEnd ) max -= snapLength; - t = qMin( max, t.quantize( snapSize ) ); - } - else - { // Otherwise, resize in fixed increments - // Don't resize to less than 1 tick - MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos; - MidiTime minLength = MidiTime( initialLength % snapLength ); - if (minLength < 1) minLength += snapLength; - MidiTime offset = MidiTime(t - m_initialTCOPos).quantize( snapSize ); - t = qMin( m_initialTCOEnd - minLength, m_initialTCOPos + offset ); - } - - MidiTime oldPos = m_tco->startPosition(); - if( m_tco->length() + ( oldPos - t ) >= 1 ) - { - m_tco->movePosition( t ); - m_tco->changeLength( m_tco->length() + ( oldPos - t ) ); - sTco->setStartTimeOffset( sTco->startTimeOffset() + ( oldPos - t ) ); - } - } - } - s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). - arg( m_tco->length().getBar() ). - arg( m_tco->length().getTicks() % - MidiTime::ticksPerBar() ). - arg( m_tco->startPosition().getBar() + 1 ). - arg( m_tco->startPosition().getTicks() % - MidiTime::ticksPerBar() ). - arg( m_tco->endPosition().getBar() + 1 ). - arg( m_tco->endPosition().getTicks() % - MidiTime::ticksPerBar() ) ); - s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); - } - else - { - SampleTCO * sTco = dynamic_cast( m_tco ); - if( ( me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize() ) - || ( me->x() < RESIZE_GRIP_WIDTH && !me->buttons() && sTco && !m_tco->getAutoResize() ) ) - { - setCursor( Qt::SizeHorCursor ); - } - else - { - leaveEvent( NULL ); - } - } -} - - - - -/*! \brief Handle a mouse release on this trackContentObjectView. - * - * If we're in move or resize mode, journal the change as appropriate. - * Then tidy up. - * - * \param me The QMouseEvent to handle. - */ -void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me ) -{ - // If the CopySelection was chosen as the action due to mouse movement, - // it will have been cleared. At this point Toggle is the desired action. - // An active StringPairDrag will prevent this method from being called, - // so a real CopySelection would not have occurred. - if( m_action == CopySelection || - ( m_action == ToggleSelected && mouseMovedDistance( me, 2 ) == false ) ) - { - setSelected( !isSelected() ); - } - - if( m_action == Move || m_action == Resize || m_action == ResizeLeft ) - { - // TODO: Fix m_tco->setJournalling() consistency - m_tco->setJournalling( true ); - } - m_action = NoAction; - delete m_hint; - m_hint = NULL; - s_textFloat->hide(); - leaveEvent( NULL ); - selectableObject::mouseReleaseEvent( me ); -} - - - - -/*! \brief Set up the context menu for this trackContentObjectView. - * - * Set up the various context menu events that can apply to a - * track content object view. - * - * \param cme The QContextMenuEvent to add the actions to. - */ -void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) -{ - // Depending on whether we right-clicked a selection or an individual TCO we will have - // different labels for the actions. - bool individualTCO = getClickedTCOs().size() <= 1; - - if( cme->modifiers() ) - { - return; - } - - QMenu contextMenu( this ); - - if( fixedTCOs() == false ) - { - contextMenu.addAction( - embed::getIconPixmap( "cancel" ), - individualTCO - ? tr("Delete (middle mousebutton)") - : tr("Delete selection (middle mousebutton)"), - [this](){ contextMenuAction( Remove ); } ); - - contextMenu.addSeparator(); - - contextMenu.addAction( - embed::getIconPixmap( "edit_cut" ), - individualTCO - ? tr("Cut") - : tr("Cut selection"), - [this](){ contextMenuAction( Cut ); } ); - } - - contextMenu.addAction( - embed::getIconPixmap( "edit_copy" ), - individualTCO - ? tr("Copy") - : tr("Copy selection"), - [this](){ contextMenuAction( Copy ); } ); - - contextMenu.addAction( - embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), - [this](){ contextMenuAction( Paste ); } ); - - contextMenu.addSeparator(); - - contextMenu.addAction( - embed::getIconPixmap( "muted" ), - (individualTCO - ? tr("Mute/unmute (<%1> + middle click)") - : tr("Mute/unmute selection (<%1> + middle click)")).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); - - contextMenu.addSeparator(); - - contextMenu.addAction( embed::getIconPixmap( "colorize" ), - tr( "Set clip color" ), this, SLOT( changeClipColor() ) ); - contextMenu.addAction( embed::getIconPixmap( "colorize" ), - tr( "Use track color" ), this, SLOT( useTrackColor() ) ); - - constructContextMenu( &contextMenu ); - - contextMenu.exec( QCursor::pos() ); -} - -// This method processes the actions from the context menu of the TCO View. -void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) -{ - QVector active = getClickedTCOs(); - // active will be later used for the remove, copy, cut or toggleMute methods - - switch( action ) - { - case Remove: - remove( active ); - break; - case Cut: - cut( active ); - break; - case Copy: - copy( active ); - break; - case Paste: - paste(); - break; - case Mute: - toggleMute( active ); - break; - } -} - -QVector TrackContentObjectView::getClickedTCOs() -{ - // Get a list of selected selectableObjects - QVector sos = gui->songEditor()->m_editor->selectedObjects(); - - // Convert to a list of selected TCOVs - QVector selection; - selection.reserve( sos.size() ); - for( auto so: sos ) - { - TrackContentObjectView *tcov = dynamic_cast ( so ); - if( tcov != nullptr ) - { - selection.append( tcov ); - } - } - - // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked - return selection.contains(this) - ? selection - : QVector( 1, this ); -} - -void TrackContentObjectView::remove( QVector tcovs ) -{ - for( auto tcov: tcovs ) - { - // No need to check if it's nullptr because we check when building the QVector - tcov->remove(); - } -} - -void TrackContentObjectView::copy( QVector tcovs ) -{ - // For copyStringPair() - using namespace Clipboard; - - // Write the TCOs to a DataFile for copying - DataFile dataFile = createTCODataFiles( tcovs ); - - // Copy the TCO type as a key and the TCO data file to the clipboard - copyStringPair( QString( "tco_%1" ).arg( m_tco->getTrack()->type() ), - dataFile.toString() ); -} - -void TrackContentObjectView::cut( QVector tcovs ) -{ - // Copy the selected TCOs - copy( tcovs ); - - // Now that the TCOs are copied we can delete them, since we are cutting - remove( tcovs ); -} - -void TrackContentObjectView::paste() -{ - // For getMimeData() - using namespace Clipboard; - - // If possible, paste the selection on the MidiTime of the selected Track and remove it - MidiTime tcoPos = MidiTime( m_tco->startPosition() ); - - TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); - - if( tcw->pasteSelection( tcoPos, getMimeData() ) ) - { - // If we succeed on the paste we delete the TCO we pasted on - remove(); - } -} - -void TrackContentObjectView::toggleMute( QVector tcovs ) -{ - for( auto tcov: tcovs ) - { - // No need to check for nullptr because we check while building the tcovs QVector - tcov->getTrackContentObject()->toggleMute(); - } -} - - - - -/*! \brief How many pixels a bar takes for this trackContentObjectView. - * - * \return the number of pixels per bar. - */ -float TrackContentObjectView::pixelsPerBar() -{ - return m_trackView->trackContainerView()->pixelsPerBar(); -} - - -/*! \brief Save the offsets between all selected tracks and a clicked track */ -void TrackContentObjectView::setInitialOffsets() -{ - QVector so = m_trackView->trackContainerView()->selectedObjects(); - QVector offsets; - for( QVector::iterator it = so.begin(); - it != so.end(); ++it ) - { - TrackContentObjectView * tcov = - dynamic_cast( *it ); - if( tcov == NULL ) - { - continue; - } - offsets.push_back( tcov->m_tco->startPosition() - m_initialTCOPos ); - } - - m_initialOffsets = offsets; -} - - - - -/*! \brief Detect whether the mouse moved more than n pixels on screen. - * - * \param _me The QMouseEvent. - * \param distance The threshold distance that the mouse has moved to return true. - */ -bool TrackContentObjectView::mouseMovedDistance( QMouseEvent * me, int distance ) -{ - QPoint dPos = mapToGlobal( me->pos() ) - m_initialMouseGlobalPos; - const int pixelsMoved = dPos.manhattanLength(); - return ( pixelsMoved > distance || pixelsMoved < -distance ); -} - - - -/*! \brief Calculate the new position of a dragged TCO from a mouse event - * - * - * \param me The QMouseEvent - */ -MidiTime TrackContentObjectView::draggedTCOPos( QMouseEvent * me ) -{ - //Pixels per bar - const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); - // The pixel distance that the mouse has moved - const int mouseOff = mapToGlobal(me->pos()).x() - m_initialMouseGlobalPos.x(); - MidiTime newPos = m_initialTCOPos + mouseOff * MidiTime::ticksPerBar() / ppb; - MidiTime offset = newPos - m_initialTCOPos; - // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize - if ( me->button() != Qt::NoButton - || (me->modifiers() & Qt::ControlModifier) - || (me->modifiers() & Qt::AltModifier) ) - { - // We want to preserve this adjusted offset, - // even if the user switches to snapping - setInitialPos( m_initialMousePos ); - } - else if ( me->modifiers() & Qt::ShiftModifier ) - { // If shift is held, quantize position (Default in 1.2.0 and earlier) - // or end position, whichever is closest to the actual position - MidiTime startQ = newPos.quantize( gui->songEditor()->m_editor->getSnapSize() ); - // Find start position that gives snapped clip end position - MidiTime endQ = ( newPos + m_tco->length() ); - endQ = endQ.quantize( gui->songEditor()->m_editor->getSnapSize() ); - endQ = endQ - m_tco->length(); - // Select the position closest to actual position - if ( abs(newPos - startQ) < abs(newPos - endQ) ) newPos = startQ; - else newPos = endQ; - } - else - { // Otherwise, quantize moved distance (preserves user offsets) - newPos = m_initialTCOPos + offset.quantize( gui->songEditor()->m_editor->getSnapSize() ); - } - return newPos; -} - - -// Return the color that the TCO's background should be -QColor TrackContentObjectView::getColorForDisplay( QColor defaultColor ) -{ - // Get the pure TCO color - auto tcoColor = m_tco->hasColor() - ? m_tco->usesCustomClipColor() - ? m_tco->color() - : m_tco->getTrack()->color() - : defaultColor; - - // Set variables - QColor c, mutedCustomColor; - bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted(); - mutedCustomColor = tcoColor; - mutedCustomColor.setHsv( mutedCustomColor.hsvHue(), mutedCustomColor.hsvSaturation() / 4, mutedCustomColor.value() ); - - // Change the pure color by state: selected, muted, colored, normal - if( isSelected() ) - { - c = m_tco->hasColor() - ? ( muted - ? mutedCustomColor.darker( 350 ) - : tcoColor.darker( 150 ) ) - : selectedColor(); - } - else - { - if( muted ) - { - c = m_tco->hasColor() - ? mutedCustomColor.darker( 250 ) - : mutedBackgroundColor(); - } - else - { - c = tcoColor; - } - } - - // Return color to caller - return c; -} - - - - -// =========================================================================== -// track -// =========================================================================== - /*! \brief Create a new (empty) track object * * The track object is the whole track, linking its contents, its diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4368f192be1..ceee236032e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -34,6 +34,7 @@ SET(LMMS_SRCS gui/TimeLineWidget.cpp gui/ToolPluginView.cpp gui/TrackContainerView.cpp + gui/TrackContentObjectView.cpp gui/TrackView.cpp gui/dialogs/FileDialog.cpp diff --git a/src/gui/TrackContentObjectView.cpp b/src/gui/TrackContentObjectView.cpp new file mode 100644 index 00000000000..e784724a0a7 --- /dev/null +++ b/src/gui/TrackContentObjectView.cpp @@ -0,0 +1,1242 @@ +/* + * TrackContentObjectView.cpp - implementation of TrackContentObjectView class + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "TrackContentObjectView.h" + +#include +#include +#include +#include + +#include "AutomationPattern.h" +#include "Clipboard.h" +#include "ColorChooser.h" +#include "embed.h" +#include "GuiApplication.h" +#include "SampleTrack.h" +#include "SongEditor.h" +#include "StringPairDrag.h" +#include "TextFloat.h" +#include "TrackContainer.h" +#include "TrackContainerView.h" +#include "TrackView.h" + + +/*! The width of the resize grip in pixels + */ +const int RESIZE_GRIP_WIDTH = 4; + + +/*! A pointer for that text bubble used when moving segments, etc. + * + * In a number of situations, LMMS displays a floating text bubble + * beside the cursor as you move or resize elements of a track about. + * This pointer keeps track of it, as you only ever need one at a time. + */ +TextFloat * TrackContentObjectView::s_textFloat = NULL; + + +/*! \brief Create a new trackContentObjectView + * + * Creates a new track content object view for the given + * track content object in the given track view. + * + * \param _tco The track content object to be displayed + * \param _tv The track view that will contain the new object + */ +TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco, + TrackView * tv ) : + selectableObject( tv->getTrackContentWidget() ), + ModelView( NULL, this ), + m_tco( tco ), + m_trackView( tv ), + m_action( NoAction ), + m_initialMousePos( QPoint( 0, 0 ) ), + m_initialMouseGlobalPos( QPoint( 0, 0 ) ), + m_initialTCOPos( MidiTime(0) ), + m_initialTCOEnd( MidiTime(0) ), + m_initialOffsets( QVector() ), + m_hint( NULL ), + m_mutedColor( 0, 0, 0 ), + m_mutedBackgroundColor( 0, 0, 0 ), + m_selectedColor( 0, 0, 0 ), + m_textColor( 0, 0, 0 ), + m_textShadowColor( 0, 0, 0 ), + m_BBPatternBackground( 0, 0, 0 ), + m_gradient( true ), + m_mouseHotspotHand( 0, 0 ), + m_cursorSetYet( false ), + m_needsUpdate( true ) +{ + if( s_textFloat == NULL ) + { + s_textFloat = new TextFloat; + s_textFloat->setPixmap( embed::getIconPixmap( "clock" ) ); + } + + setAttribute( Qt::WA_OpaquePaintEvent, true ); + setAttribute( Qt::WA_DeleteOnClose, true ); + setFocusPolicy( Qt::StrongFocus ); + setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); + move( 0, 0 ); + show(); + + setFixedHeight( tv->getTrackContentWidget()->height() - 1); + setAcceptDrops( true ); + setMouseTracking( true ); + + connect( m_tco, SIGNAL( lengthChanged() ), + this, SLOT( updateLength() ) ); + connect( gui->songEditor()->m_editor->zoomingModel(), SIGNAL( dataChanged() ), this, SLOT( updateLength() ) ); + connect( m_tco, SIGNAL( positionChanged() ), + this, SLOT( updatePosition() ) ); + connect( m_tco, SIGNAL( destroyedTCO() ), this, SLOT( close() ) ); + setModel( m_tco ); + connect( m_tco, SIGNAL( trackColorChanged() ), this, SLOT( update() ) ); + connect( m_trackView->getTrackOperationsWidget(), SIGNAL( colorParented() ), this, SLOT( useTrackColor() ) ); + + m_trackView->getTrackContentWidget()->addTCOView( this ); + updateLength(); + updatePosition(); +} + + + + +/*! \brief Destroy a trackContentObjectView + * + * Destroys the given track content object view. + * + */ +TrackContentObjectView::~TrackContentObjectView() +{ + delete m_hint; + // we have to give our track-container the focus because otherwise the + // op-buttons of our track-widgets could become focus and when the user + // presses space for playing song, just one of these buttons is pressed + // which results in unwanted effects + m_trackView->trackContainerView()->setFocus(); +} + + +/*! \brief Update a TrackContentObjectView + * + * TCO's get drawn only when needed, + * and when a TCO is updated, + * it needs to be redrawn. + * + */ +void TrackContentObjectView::update() +{ + if( !m_cursorSetYet ) + { + setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); + m_cursorSetYet = true; + } + + if( fixedTCOs() ) + { + updateLength(); + } + m_needsUpdate = true; + selectableObject::update(); +} + + + +/*! \brief Does this trackContentObjectView have a fixed TCO? + * + * Returns whether the containing trackView has fixed + * TCOs. + * + * \todo What the hell is a TCO here - track content object? And in + * what circumstance are they fixed? + */ +bool TrackContentObjectView::fixedTCOs() +{ + return m_trackView->trackContainerView()->fixedTCOs(); +} + + + +// qproperty access functions, to be inherited & used by TCOviews +//! \brief CSS theming qproperty access method +QColor TrackContentObjectView::mutedColor() const +{ return m_mutedColor; } + +QColor TrackContentObjectView::mutedBackgroundColor() const +{ return m_mutedBackgroundColor; } + +QColor TrackContentObjectView::selectedColor() const +{ return m_selectedColor; } + +QColor TrackContentObjectView::textColor() const +{ return m_textColor; } + +QColor TrackContentObjectView::textBackgroundColor() const +{ + return m_textBackgroundColor; +} + +QColor TrackContentObjectView::textShadowColor() const +{ return m_textShadowColor; } + +QColor TrackContentObjectView::BBPatternBackground() const +{ return m_BBPatternBackground; } + +bool TrackContentObjectView::gradient() const +{ return m_gradient; } + +//! \brief CSS theming qproperty access method +void TrackContentObjectView::setMutedColor( const QColor & c ) +{ m_mutedColor = QColor( c ); } + +void TrackContentObjectView::setMutedBackgroundColor( const QColor & c ) +{ m_mutedBackgroundColor = QColor( c ); } + +void TrackContentObjectView::setSelectedColor( const QColor & c ) +{ m_selectedColor = QColor( c ); } + +void TrackContentObjectView::setTextColor( const QColor & c ) +{ m_textColor = QColor( c ); } + +void TrackContentObjectView::setTextBackgroundColor( const QColor & c ) +{ + m_textBackgroundColor = c; +} + +void TrackContentObjectView::setTextShadowColor( const QColor & c ) +{ m_textShadowColor = QColor( c ); } + +void TrackContentObjectView::setBBPatternBackground( const QColor & c ) +{ m_BBPatternBackground = QColor( c ); } + +void TrackContentObjectView::setGradient( const bool & b ) +{ m_gradient = b; } + +void TrackContentObjectView::setMouseHotspotHand(const QSize & s) +{ + m_mouseHotspotHand = s; +} + +// access needsUpdate member variable +bool TrackContentObjectView::needsUpdate() +{ return m_needsUpdate; } +void TrackContentObjectView::setNeedsUpdate( bool b ) +{ m_needsUpdate = b; } + +/*! \brief Close a trackContentObjectView + * + * Closes a track content object view by asking the track + * view to remove us and then asking the QWidget to close us. + * + * \return Boolean state of whether the QWidget was able to close. + */ +bool TrackContentObjectView::close() +{ + m_trackView->getTrackContentWidget()->removeTCOView( this ); + return QWidget::close(); +} + + + + +/*! \brief Removes a trackContentObjectView from its track view. + * + * Like the close() method, this asks the track view to remove this + * track content object view. However, the track content object is + * scheduled for later deletion rather than closed immediately. + * + */ +void TrackContentObjectView::remove() +{ + m_trackView->getTrack()->addJournalCheckPoint(); + + // delete ourself + close(); + m_tco->deleteLater(); +} + + + + +/*! \brief Updates a trackContentObjectView's length + * + * If this track content object view has a fixed TCO, then we must + * keep the width of our parent. Otherwise, calculate our width from + * the track content object's length in pixels adding in the border. + * + */ +void TrackContentObjectView::updateLength() +{ + if( fixedTCOs() ) + { + setFixedWidth( parentWidget()->width() ); + } + else + { + setFixedWidth( + static_cast( m_tco->length() * pixelsPerBar() / + MidiTime::ticksPerBar() ) + 1 /*+ + TCO_BORDER_WIDTH * 2-1*/ ); + } + m_trackView->trackContainerView()->update(); +} + + + + +/*! \brief Updates a trackContentObjectView's position. + * + * Ask our track view to change our position. Then make sure that the + * track view is updated in case this position has changed the track + * view's length. + * + */ +void TrackContentObjectView::updatePosition() +{ + m_trackView->getTrackContentWidget()->changePosition(); + // moving a TCO can result in change of song-length etc., + // therefore we update the track-container + m_trackView->trackContainerView()->update(); +} + + + + +void TrackContentObjectView::changeClipColor() +{ + // Get a color from the user + QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )->getColor( m_tco->color() ); + if( ! new_color.isValid() ) + { return; } + + // Use that color + m_tco->setColor( new_color ); + m_tco->useCustomClipColor( true ); + update(); +} + + + +void TrackContentObjectView::useTrackColor() +{ + m_tco->useCustomClipColor( false ); + update(); +} + + + + + +/*! \brief Change the trackContentObjectView's display when something + * being dragged enters it. + * + * We need to notify Qt to change our display if something being + * dragged has entered our 'airspace'. + * + * \param dee The QDragEnterEvent to watch. + */ +void TrackContentObjectView::dragEnterEvent( QDragEnterEvent * dee ) +{ + TrackContentWidget * tcw = getTrackView()->getTrackContentWidget(); + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + if( tcw->canPasteSelection( tcoPos, dee ) == false ) + { + dee->ignore(); + } + else + { + StringPairDrag::processDragEnterEvent( dee, "tco_" + + QString::number( m_tco->getTrack()->type() ) ); + } +} + + + + +/*! \brief Handle something being dropped on this trackContentObjectView. + * + * When something has been dropped on this trackContentObjectView, and + * it's a track content object, then use an instance of our dataFile reader + * to take the xml of the track content object and turn it into something + * we can write over our current state. + * + * \param de The QDropEvent to handle. + */ +void TrackContentObjectView::dropEvent( QDropEvent * de ) +{ + QString type = StringPairDrag::decodeKey( de ); + QString value = StringPairDrag::decodeValue( de ); + + // Track must be the same type to paste into + if( type != ( "tco_" + QString::number( m_tco->getTrack()->type() ) ) ) + { + return; + } + + // Defer to rubberband paste if we're in that mode + if( m_trackView->trackContainerView()->allowRubberband() == true ) + { + TrackContentWidget * tcw = getTrackView()->getTrackContentWidget(); + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + if( tcw->pasteSelection( tcoPos, de ) == true ) + { + de->accept(); + } + return; + } + + // Don't allow pasting a tco into itself. + QObject* qwSource = de->source(); + if( qwSource != NULL && + dynamic_cast( qwSource ) == this ) + { + return; + } + + // Copy state into existing tco + DataFile dataFile( value.toUtf8() ); + MidiTime pos = m_tco->startPosition(); + QDomElement tcos = dataFile.content().firstChildElement( "tcos" ); + m_tco->restoreState( tcos.firstChildElement().firstChildElement() ); + m_tco->movePosition( pos ); + AutomationPattern::resolveAllIDs(); + de->accept(); +} + + + + +/*! \brief Handle a dragged selection leaving our 'airspace'. + * + * \param e The QEvent to watch. + */ +void TrackContentObjectView::leaveEvent( QEvent * e ) +{ + if( cursor().shape() != Qt::BitmapCursor ) + { + setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) ); + } + if( e != NULL ) + { + QWidget::leaveEvent( e ); + } +} + +/*! \brief Create a DataFile suitable for copying multiple trackContentObjects. + * + * trackContentObjects in the vector are written to the "tcos" node in the + * DataFile. The trackContentObjectView's initial mouse position is written + * to the "initialMouseX" node in the DataFile. When dropped on a track, + * this is used to create copies of the TCOs. + * + * \param tcos The trackContectObjects to save in a DataFile + */ +DataFile TrackContentObjectView::createTCODataFiles( + const QVector & tcoViews) const +{ + Track * t = m_trackView->getTrack(); + TrackContainer * tc = t->trackContainer(); + DataFile dataFile( DataFile::DragNDropData ); + QDomElement tcoParent = dataFile.createElement( "tcos" ); + + typedef QVector tcoViewVector; + for( tcoViewVector::const_iterator it = tcoViews.begin(); + it != tcoViews.end(); ++it ) + { + // Insert into the dom under the "tcos" element + Track* tcoTrack = ( *it )->m_trackView->getTrack(); + int trackIndex = tc->tracks().indexOf( tcoTrack ); + QDomElement tcoElement = dataFile.createElement( "tco" ); + tcoElement.setAttribute( "trackIndex", trackIndex ); + tcoElement.setAttribute( "trackType", tcoTrack->type() ); + tcoElement.setAttribute( "trackName", tcoTrack->name() ); + ( *it )->m_tco->saveState( dataFile, tcoElement ); + tcoParent.appendChild( tcoElement ); + } + + dataFile.content().appendChild( tcoParent ); + + // Add extra metadata needed for calculations later + int initialTrackIndex = tc->tracks().indexOf( t ); + if( initialTrackIndex < 0 ) + { + printf("Failed to find selected track in the TrackContainer.\n"); + return dataFile; + } + QDomElement metadata = dataFile.createElement( "copyMetadata" ); + // initialTrackIndex is the index of the track that was touched + metadata.setAttribute( "initialTrackIndex", initialTrackIndex ); + metadata.setAttribute( "trackContainerId", tc->id() ); + // grabbedTCOPos is the pos of the bar containing the TCO we grabbed + metadata.setAttribute( "grabbedTCOPos", m_tco->startPosition() ); + + dataFile.content().appendChild( metadata ); + + return dataFile; +} + +void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & painter) +{ + if (text.trimmed() == "") + { + return; + } + + painter.setRenderHint( QPainter::TextAntialiasing ); + + QFont labelFont = this->font(); + labelFont.setHintingPreference( QFont::PreferFullHinting ); + painter.setFont( labelFont ); + + const int textTop = TCO_BORDER_WIDTH + 1; + const int textLeft = TCO_BORDER_WIDTH + 3; + + QFontMetrics fontMetrics(labelFont); + QString elidedPatternName = fontMetrics.elidedText(text, Qt::ElideMiddle, width() - 2 * textLeft); + + if (elidedPatternName.length() < 2) + { + elidedPatternName = text.trimmed(); + } + + painter.fillRect(QRect(0, 0, width(), fontMetrics.height() + 2 * textTop), textBackgroundColor()); + + int const finalTextTop = textTop + fontMetrics.ascent(); + painter.setPen(textShadowColor()); + painter.drawText( textLeft + 1, finalTextTop + 1, elidedPatternName ); + painter.setPen( textColor() ); + painter.drawText( textLeft, finalTextTop, elidedPatternName ); +} + +/*! \brief Handle a mouse press on this trackContentObjectView. + * + * Handles the various ways in which a trackContentObjectView can be + * used with a click of a mouse button. + * + * * If our container supports rubber band selection then handle + * selection events. + * * or if shift-left button, add this object to the selection + * * or if ctrl-left button, start a drag-copy event + * * or if just plain left button, resize if we're resizeable + * * or if ctrl-middle button, mute the track content object + * * or if middle button, maybe delete the track content object. + * + * \param me The QMouseEvent to handle. + */ +void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) +{ + // Right now, active is only used on right/mid clicks actions, so we use a ternary operator + // to avoid the overhead of calling getClickedTCOs when it's not used + auto active = me->button() == Qt::LeftButton + ? QVector() + : getClickedTCOs(); + + setInitialPos( me->pos() ); + setInitialOffsets(); + if( !fixedTCOs() && me->button() == Qt::LeftButton ) + { + if( me->modifiers() & Qt::ControlModifier ) + { + if( isSelected() ) + { + m_action = CopySelection; + } + else + { + m_action = ToggleSelected; + } + } + else if( !me->modifiers() + || (me->modifiers() & Qt::AltModifier) + || (me->modifiers() & Qt::ShiftModifier) ) + { + if( isSelected() ) + { + m_action = MoveSelection; + } + else + { + gui->songEditor()->m_editor->selectAllTcos( false ); + m_tco->addJournalCheckPoint(); + + // move or resize + m_tco->setJournalling( false ); + + setInitialPos( me->pos() ); + setInitialOffsets(); + + SampleTCO * sTco = dynamic_cast( m_tco ); + if( me->x() < RESIZE_GRIP_WIDTH && sTco + && !m_tco->getAutoResize() ) + { + m_action = ResizeLeft; + setCursor( Qt::SizeHorCursor ); + } + else if( m_tco->getAutoResize() || me->x() < width() - RESIZE_GRIP_WIDTH ) + { + m_action = Move; + setCursor( Qt::SizeAllCursor ); + } + else + { + m_action = Resize; + setCursor( Qt::SizeHorCursor ); + } + + if( m_action == Move ) + { + s_textFloat->setTitle( tr( "Current position" ) ); + s_textFloat->setText( QString( "%1:%2" ). + arg( m_tco->startPosition().getBar() + 1 ). + arg( m_tco->startPosition().getTicks() % + MidiTime::ticksPerBar() ) ); + } + else if( m_action == Resize || m_action == ResizeLeft ) + { + s_textFloat->setTitle( tr( "Current length" ) ); + s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). + arg( m_tco->length().getBar() ). + arg( m_tco->length().getTicks() % + MidiTime::ticksPerBar() ). + arg( m_tco->startPosition().getBar() + 1 ). + arg( m_tco->startPosition().getTicks() % + MidiTime::ticksPerBar() ). + arg( m_tco->endPosition().getBar() + 1 ). + arg( m_tco->endPosition().getTicks() % + MidiTime::ticksPerBar() ) ); + } + // s_textFloat->reparent( this ); + // setup text-float as if TCO was already moved/resized + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); + s_textFloat->show(); + } + + delete m_hint; + QString hint = m_action == Move || m_action == MoveSelection + ? tr( "Press <%1> and drag to make a copy." ) + : tr( "Press <%1> for free resizing." ); + m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_CTRL_KEY), + embed::getIconPixmap( "hint" ), 0 ); + } + } + else if( me->button() == Qt::RightButton ) + { + if( me->modifiers() & Qt::ControlModifier ) + { + toggleMute( active ); + } + else if( me->modifiers() & Qt::ShiftModifier && !fixedTCOs() ) + { + remove( active ); + } + } + else if( me->button() == Qt::MidButton ) + { + if( me->modifiers() & Qt::ControlModifier ) + { + toggleMute( active ); + } + else if( !fixedTCOs() ) + { + remove( active ); + } + } +} + + + + +/*! \brief Handle a mouse movement (drag) on this trackContentObjectView. + * + * Handles the various ways in which a trackContentObjectView can be + * used with a mouse drag. + * + * * If in move mode, move ourselves in the track, + * * or if in move-selection mode, move the entire selection, + * * or if in resize mode, resize ourselves, + * * otherwise ??? + * + * \param me The QMouseEvent to handle. + * \todo what does the final else case do here? + */ +void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) +{ + if( m_action == CopySelection || m_action == ToggleSelected ) + { + if( mouseMovedDistance( me, 2 ) == true ) + { + QVector tcoViews; + if( m_action == CopySelection ) + { + // Collect all selected TCOs + QVector so = + m_trackView->trackContainerView()->selectedObjects(); + for( auto it = so.begin(); it != so.end(); ++it ) + { + TrackContentObjectView * tcov = + dynamic_cast( *it ); + if( tcov != NULL ) + { + tcoViews.push_back( tcov ); + } + } + } + else + { + gui->songEditor()->m_editor->selectAllTcos( false ); + tcoViews.push_back( this ); + } + // Clear the action here because mouseReleaseEvent will not get + // triggered once we go into drag. + m_action = NoAction; + + // Write the TCOs to the DataFile for copying + DataFile dataFile = createTCODataFiles( tcoViews ); + + // TODO -- thumbnail for all selected + QPixmap thumbnail = grab().scaled( + 128, 128, + Qt::KeepAspectRatio, + Qt::SmoothTransformation ); + new StringPairDrag( QString( "tco_%1" ).arg( + m_tco->getTrack()->type() ), + dataFile.toString(), thumbnail, this ); + } + } + + if( me->modifiers() & Qt::ControlModifier ) + { + delete m_hint; + m_hint = NULL; + } + + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + if( m_action == Move ) + { + MidiTime newPos = draggedTCOPos( me ); + + m_tco->movePosition(newPos); + newPos = m_tco->startPosition(); // Get the real position the TCO was dragged to for the label + m_trackView->getTrackContentWidget()->changePosition(); + s_textFloat->setText( QString( "%1:%2" ). + arg( newPos.getBar() + 1 ). + arg( newPos.getTicks() % + MidiTime::ticksPerBar() ) ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) ); + } + else if( m_action == MoveSelection ) + { + // 1: Find the position we want to move the grabbed TCO to + MidiTime newPos = draggedTCOPos( me ); + + // 2: Handle moving the other selected TCOs the same distance + QVector so = + m_trackView->trackContainerView()->selectedObjects(); + QVector tcos; // List of selected clips + int leftmost = 0; // Leftmost clip's offset from grabbed clip + // Populate tcos, find leftmost + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView * tcov = + dynamic_cast( *it ); + if( tcov == NULL ) { continue; } + tcos.push_back( tcov->m_tco ); + int index = std::distance( so.begin(), it ); + leftmost = min (leftmost, m_initialOffsets[index].getTicks() ); + } + // Make sure the leftmost clip doesn't get moved to a negative position + if ( newPos.getTicks() + leftmost < 0 ) { newPos = -leftmost; } + + for( QVector::iterator it = tcos.begin(); + it != tcos.end(); ++it ) + { + int index = std::distance( tcos.begin(), it ); + ( *it )->movePosition( newPos + m_initialOffsets[index] ); + } + } + else if( m_action == Resize || m_action == ResizeLeft ) + { + // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize + const bool unquantized = (me->modifiers() & Qt::ControlModifier) || (me->modifiers() & Qt::AltModifier); + const float snapSize = gui->songEditor()->m_editor->getSnapSize(); + // Length in ticks of one snap increment + const MidiTime snapLength = MidiTime( (int)(snapSize * MidiTime::ticksPerBar()) ); + + if( m_action == Resize ) + { + // The clip's new length + MidiTime l = static_cast( me->x() * MidiTime::ticksPerBar() / ppb ); + + if ( unquantized ) + { // We want to preserve this adjusted offset, + // even if the user switches to snapping later + setInitialPos( m_initialMousePos ); + // Don't resize to less than 1 tick + m_tco->changeLength( qMax( 1, l ) ); + } + else if ( me->modifiers() & Qt::ShiftModifier ) + { // If shift is held, quantize clip's end position + MidiTime end = MidiTime( m_initialTCOPos + l ).quantize( snapSize ); + // The end position has to be after the clip's start + MidiTime min = m_initialTCOPos.quantize( snapSize ); + if ( min <= m_initialTCOPos ) min += snapLength; + m_tco->changeLength( qMax(min - m_initialTCOPos, end - m_initialTCOPos) ); + } + else + { // Otherwise, resize in fixed increments + MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos; + MidiTime offset = MidiTime( l - initialLength ).quantize( snapSize ); + // Don't resize to less than 1 tick + MidiTime min = MidiTime( initialLength % snapLength ); + if (min < 1) min += snapLength; + m_tco->changeLength( qMax( min, initialLength + offset) ); + } + } + else + { + SampleTCO * sTco = dynamic_cast( m_tco ); + if( sTco ) + { + const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x(); + + MidiTime t = qMax( 0, (int) + m_trackView->trackContainerView()->currentPosition() + + static_cast( x * MidiTime::ticksPerBar() / ppb ) ); + + if( unquantized ) + { // We want to preserve this adjusted offset, + // even if the user switches to snapping later + setInitialPos( m_initialMousePos ); + //Don't resize to less than 1 tick + t = qMin( m_initialTCOEnd - 1, t); + } + else if( me->modifiers() & Qt::ShiftModifier ) + { // If shift is held, quantize clip's start position + // Don't let the start position move past the end position + MidiTime max = m_initialTCOEnd.quantize( snapSize ); + if ( max >= m_initialTCOEnd ) max -= snapLength; + t = qMin( max, t.quantize( snapSize ) ); + } + else + { // Otherwise, resize in fixed increments + // Don't resize to less than 1 tick + MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos; + MidiTime minLength = MidiTime( initialLength % snapLength ); + if (minLength < 1) minLength += snapLength; + MidiTime offset = MidiTime(t - m_initialTCOPos).quantize( snapSize ); + t = qMin( m_initialTCOEnd - minLength, m_initialTCOPos + offset ); + } + + MidiTime oldPos = m_tco->startPosition(); + if( m_tco->length() + ( oldPos - t ) >= 1 ) + { + m_tco->movePosition( t ); + m_tco->changeLength( m_tco->length() + ( oldPos - t ) ); + sTco->setStartTimeOffset( sTco->startTimeOffset() + ( oldPos - t ) ); + } + } + } + s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). + arg( m_tco->length().getBar() ). + arg( m_tco->length().getTicks() % + MidiTime::ticksPerBar() ). + arg( m_tco->startPosition().getBar() + 1 ). + arg( m_tco->startPosition().getTicks() % + MidiTime::ticksPerBar() ). + arg( m_tco->endPosition().getBar() + 1 ). + arg( m_tco->endPosition().getTicks() % + MidiTime::ticksPerBar() ) ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); + } + else + { + SampleTCO * sTco = dynamic_cast( m_tco ); + if( ( me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize() ) + || ( me->x() < RESIZE_GRIP_WIDTH && !me->buttons() && sTco && !m_tco->getAutoResize() ) ) + { + setCursor( Qt::SizeHorCursor ); + } + else + { + leaveEvent( NULL ); + } + } +} + + + + +/*! \brief Handle a mouse release on this trackContentObjectView. + * + * If we're in move or resize mode, journal the change as appropriate. + * Then tidy up. + * + * \param me The QMouseEvent to handle. + */ +void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me ) +{ + // If the CopySelection was chosen as the action due to mouse movement, + // it will have been cleared. At this point Toggle is the desired action. + // An active StringPairDrag will prevent this method from being called, + // so a real CopySelection would not have occurred. + if( m_action == CopySelection || + ( m_action == ToggleSelected && mouseMovedDistance( me, 2 ) == false ) ) + { + setSelected( !isSelected() ); + } + + if( m_action == Move || m_action == Resize || m_action == ResizeLeft ) + { + // TODO: Fix m_tco->setJournalling() consistency + m_tco->setJournalling( true ); + } + m_action = NoAction; + delete m_hint; + m_hint = NULL; + s_textFloat->hide(); + leaveEvent( NULL ); + selectableObject::mouseReleaseEvent( me ); +} + + + + +/*! \brief Set up the context menu for this trackContentObjectView. + * + * Set up the various context menu events that can apply to a + * track content object view. + * + * \param cme The QContextMenuEvent to add the actions to. + */ +void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme ) +{ + // Depending on whether we right-clicked a selection or an individual TCO we will have + // different labels for the actions. + bool individualTCO = getClickedTCOs().size() <= 1; + + if( cme->modifiers() ) + { + return; + } + + QMenu contextMenu( this ); + + if( fixedTCOs() == false ) + { + contextMenu.addAction( + embed::getIconPixmap( "cancel" ), + individualTCO + ? tr("Delete (middle mousebutton)") + : tr("Delete selection (middle mousebutton)"), + [this](){ contextMenuAction( Remove ); } ); + + contextMenu.addSeparator(); + + contextMenu.addAction( + embed::getIconPixmap( "edit_cut" ), + individualTCO + ? tr("Cut") + : tr("Cut selection"), + [this](){ contextMenuAction( Cut ); } ); + } + + contextMenu.addAction( + embed::getIconPixmap( "edit_copy" ), + individualTCO + ? tr("Copy") + : tr("Copy selection"), + [this](){ contextMenuAction( Copy ); } ); + + contextMenu.addAction( + embed::getIconPixmap( "edit_paste" ), + tr( "Paste" ), + [this](){ contextMenuAction( Paste ); } ); + + contextMenu.addSeparator(); + + contextMenu.addAction( + embed::getIconPixmap( "muted" ), + (individualTCO + ? tr("Mute/unmute (<%1> + middle click)") + : tr("Mute/unmute selection (<%1> + middle click)")).arg(UI_CTRL_KEY), + [this](){ contextMenuAction( Mute ); } ); + + contextMenu.addSeparator(); + + contextMenu.addAction( embed::getIconPixmap( "colorize" ), + tr( "Set clip color" ), this, SLOT( changeClipColor() ) ); + contextMenu.addAction( embed::getIconPixmap( "colorize" ), + tr( "Use track color" ), this, SLOT( useTrackColor() ) ); + + constructContextMenu( &contextMenu ); + + contextMenu.exec( QCursor::pos() ); +} + +// This method processes the actions from the context menu of the TCO View. +void TrackContentObjectView::contextMenuAction( ContextMenuAction action ) +{ + QVector active = getClickedTCOs(); + // active will be later used for the remove, copy, cut or toggleMute methods + + switch( action ) + { + case Remove: + remove( active ); + break; + case Cut: + cut( active ); + break; + case Copy: + copy( active ); + break; + case Paste: + paste(); + break; + case Mute: + toggleMute( active ); + break; + } +} + +QVector TrackContentObjectView::getClickedTCOs() +{ + // Get a list of selected selectableObjects + QVector sos = gui->songEditor()->m_editor->selectedObjects(); + + // Convert to a list of selected TCOVs + QVector selection; + selection.reserve( sos.size() ); + for( auto so: sos ) + { + TrackContentObjectView *tcov = dynamic_cast ( so ); + if( tcov != nullptr ) + { + selection.append( tcov ); + } + } + + // If we clicked part of the selection, affect all selected clips. Otherwise affect the clip we clicked + return selection.contains(this) + ? selection + : QVector( 1, this ); +} + +void TrackContentObjectView::remove( QVector tcovs ) +{ + for( auto tcov: tcovs ) + { + // No need to check if it's nullptr because we check when building the QVector + tcov->remove(); + } +} + +void TrackContentObjectView::copy( QVector tcovs ) +{ + // For copyStringPair() + using namespace Clipboard; + + // Write the TCOs to a DataFile for copying + DataFile dataFile = createTCODataFiles( tcovs ); + + // Copy the TCO type as a key and the TCO data file to the clipboard + copyStringPair( QString( "tco_%1" ).arg( m_tco->getTrack()->type() ), + dataFile.toString() ); +} + +void TrackContentObjectView::cut( QVector tcovs ) +{ + // Copy the selected TCOs + copy( tcovs ); + + // Now that the TCOs are copied we can delete them, since we are cutting + remove( tcovs ); +} + +void TrackContentObjectView::paste() +{ + // For getMimeData() + using namespace Clipboard; + + // If possible, paste the selection on the MidiTime of the selected Track and remove it + MidiTime tcoPos = MidiTime( m_tco->startPosition() ); + + TrackContentWidget *tcw = getTrackView()->getTrackContentWidget(); + + if( tcw->pasteSelection( tcoPos, getMimeData() ) ) + { + // If we succeed on the paste we delete the TCO we pasted on + remove(); + } +} + +void TrackContentObjectView::toggleMute( QVector tcovs ) +{ + for( auto tcov: tcovs ) + { + // No need to check for nullptr because we check while building the tcovs QVector + tcov->getTrackContentObject()->toggleMute(); + } +} + + + + +/*! \brief How many pixels a bar takes for this trackContentObjectView. + * + * \return the number of pixels per bar. + */ +float TrackContentObjectView::pixelsPerBar() +{ + return m_trackView->trackContainerView()->pixelsPerBar(); +} + + +/*! \brief Save the offsets between all selected tracks and a clicked track */ +void TrackContentObjectView::setInitialOffsets() +{ + QVector so = m_trackView->trackContainerView()->selectedObjects(); + QVector offsets; + for( QVector::iterator it = so.begin(); + it != so.end(); ++it ) + { + TrackContentObjectView * tcov = + dynamic_cast( *it ); + if( tcov == NULL ) + { + continue; + } + offsets.push_back( tcov->m_tco->startPosition() - m_initialTCOPos ); + } + + m_initialOffsets = offsets; +} + + + + +/*! \brief Detect whether the mouse moved more than n pixels on screen. + * + * \param _me The QMouseEvent. + * \param distance The threshold distance that the mouse has moved to return true. + */ +bool TrackContentObjectView::mouseMovedDistance( QMouseEvent * me, int distance ) +{ + QPoint dPos = mapToGlobal( me->pos() ) - m_initialMouseGlobalPos; + const int pixelsMoved = dPos.manhattanLength(); + return ( pixelsMoved > distance || pixelsMoved < -distance ); +} + + + +/*! \brief Calculate the new position of a dragged TCO from a mouse event + * + * + * \param me The QMouseEvent + */ +MidiTime TrackContentObjectView::draggedTCOPos( QMouseEvent * me ) +{ + //Pixels per bar + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + // The pixel distance that the mouse has moved + const int mouseOff = mapToGlobal(me->pos()).x() - m_initialMouseGlobalPos.x(); + MidiTime newPos = m_initialTCOPos + mouseOff * MidiTime::ticksPerBar() / ppb; + MidiTime offset = newPos - m_initialTCOPos; + // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize + if ( me->button() != Qt::NoButton + || (me->modifiers() & Qt::ControlModifier) + || (me->modifiers() & Qt::AltModifier) ) + { + // We want to preserve this adjusted offset, + // even if the user switches to snapping + setInitialPos( m_initialMousePos ); + } + else if ( me->modifiers() & Qt::ShiftModifier ) + { // If shift is held, quantize position (Default in 1.2.0 and earlier) + // or end position, whichever is closest to the actual position + MidiTime startQ = newPos.quantize( gui->songEditor()->m_editor->getSnapSize() ); + // Find start position that gives snapped clip end position + MidiTime endQ = ( newPos + m_tco->length() ); + endQ = endQ.quantize( gui->songEditor()->m_editor->getSnapSize() ); + endQ = endQ - m_tco->length(); + // Select the position closest to actual position + if ( abs(newPos - startQ) < abs(newPos - endQ) ) newPos = startQ; + else newPos = endQ; + } + else + { // Otherwise, quantize moved distance (preserves user offsets) + newPos = m_initialTCOPos + offset.quantize( gui->songEditor()->m_editor->getSnapSize() ); + } + return newPos; +} + + +// Return the color that the TCO's background should be +QColor TrackContentObjectView::getColorForDisplay( QColor defaultColor ) +{ + // Get the pure TCO color + auto tcoColor = m_tco->hasColor() + ? m_tco->usesCustomClipColor() + ? m_tco->color() + : m_tco->getTrack()->color() + : defaultColor; + + // Set variables + QColor c, mutedCustomColor; + bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted(); + mutedCustomColor = tcoColor; + mutedCustomColor.setHsv( mutedCustomColor.hsvHue(), mutedCustomColor.hsvSaturation() / 4, mutedCustomColor.value() ); + + // Change the pure color by state: selected, muted, colored, normal + if( isSelected() ) + { + c = m_tco->hasColor() + ? ( muted + ? mutedCustomColor.darker( 350 ) + : tcoColor.darker( 150 ) ) + : selectedColor(); + } + else + { + if( muted ) + { + c = m_tco->hasColor() + ? mutedCustomColor.darker( 250 ) + : mutedBackgroundColor(); + } + else + { + c = tcoColor; + } + } + + // Return color to caller + return c; +} + From f7f152d32feee432deb0f0f80391a5926eb401cb Mon Sep 17 00:00:00 2001 From: M374LX Date: Fri, 27 Nov 2020 00:04:25 -0300 Subject: [PATCH 6/6] Move constants from Track.h to TrackView.h --- include/Track.h | 9 --------- include/TrackView.h | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/Track.h b/include/Track.h index 8ee9c0b7cc3..a85300f6ff6 100644 --- a/include/Track.h +++ b/include/Track.h @@ -45,13 +45,6 @@ class TrackContentObject; class TrackView; -const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; -const int TRACK_OP_WIDTH = 78; -// This shaves 150-ish pixels off track buttons, -// ruled from config: ui.compacttrackbuttons -const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96; -const int TRACK_OP_WIDTH_COMPACT = 62; - /*! The minimum track height in pixels * * Tracks can be resized by shift-dragging anywhere inside the track @@ -60,8 +53,6 @@ const int TRACK_OP_WIDTH_COMPACT = 62; const int MINIMAL_TRACK_HEIGHT = 32; const int DEFAULT_TRACK_HEIGHT = 32; -const int TCO_BORDER_WIDTH = 2; - char const *const FILENAME_FILTER = "[\\0000-\x1f\"*/:<>?\\\\|\x7f]"; diff --git a/include/TrackView.h b/include/TrackView.h index 15384219dff..dc16adb81f7 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -39,6 +39,16 @@ class FadeButton; +const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; +const int TRACK_OP_WIDTH = 78; +// This shaves 150-ish pixels off track buttons, +// ruled from config: ui.compacttrackbuttons +const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96; +const int TRACK_OP_WIDTH_COMPACT = 62; + +const int TCO_BORDER_WIDTH = 2; + + class TrackView : public QWidget, public ModelView, public JournallingObject { Q_OBJECT