2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
TrackPanel.cpp
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
and lots of other contributors
|
|
|
|
|
|
|
|
Implements TrackPanel and TrackInfo.
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
********************************************************************//*!
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
\todo
|
2014-06-03 20:30:19 +00:00
|
|
|
Refactoring of the TrackPanel, possibly as described
|
2010-01-23 19:44:49 +00:00
|
|
|
in \ref TrackPanelRefactor
|
|
|
|
|
|
|
|
*//*****************************************************************//*!
|
|
|
|
|
|
|
|
\file TrackPanel.cpp
|
2014-06-03 20:30:19 +00:00
|
|
|
\brief
|
2010-01-23 19:44:49 +00:00
|
|
|
Implements TrackPanel and TrackInfo.
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
TrackPanel.cpp is currently some of the worst code in Audacity.
|
2010-01-23 19:44:49 +00:00
|
|
|
It's not really unreadable, there's just way too much stuff in this
|
|
|
|
one file. Rather than apply a quick fix, the long-term plan
|
|
|
|
is to create a GUITrack class that knows how to draw itself
|
|
|
|
and handle events. Then this class just helps coordinate
|
|
|
|
between tracks.
|
|
|
|
|
|
|
|
Plans under discussion are described in \ref TrackPanelRefactor
|
|
|
|
|
|
|
|
*//********************************************************************/
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Documentation: Rather than have a lengthy \todo section, having
|
|
|
|
// a \todo a \file and a \page in EXACTLY that order gets Doxygen to
|
|
|
|
// put the following lengthy description of refactoring on a new page
|
2010-01-23 19:44:49 +00:00
|
|
|
// and link to it from the docs.
|
|
|
|
|
|
|
|
/*****************************************************************//**
|
|
|
|
|
|
|
|
\class TrackPanel
|
|
|
|
\brief
|
|
|
|
The TrackPanel class coordinates updates and operations on the
|
|
|
|
main part of the screen which contains multiple tracks.
|
|
|
|
|
|
|
|
It uses many other classes, but in particular it uses the
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
TrackInfo class to draw the controls area on the left of a track,
|
2010-01-23 19:44:49 +00:00
|
|
|
and the TrackArtist class to draw the actual waveforms.
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
Note that in some of the older code here, e.g., GetLabelWidth(),
|
|
|
|
"Label" means the TrackInfo plus the vertical ruler.
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
Confusing relative to LabelTrack labels.
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
The TrackPanel manages multiple tracks and their TrackInfos.
|
|
|
|
|
|
|
|
Note that with stereo tracks there will be one TrackInfo
|
|
|
|
being used by two wavetracks.
|
|
|
|
|
|
|
|
*//*****************************************************************//**
|
|
|
|
|
|
|
|
\class TrackInfo
|
|
|
|
\brief
|
2014-06-03 20:30:19 +00:00
|
|
|
The TrackInfo is shown to the side of a track
|
2010-01-23 19:44:49 +00:00
|
|
|
It has the menus, pan and gain controls displayed in it.
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
So "Info" is somewhat a misnomer. Should possibly be "TrackControls".
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
TrackPanel and not TrackInfo takes care of the functionality for
|
2010-01-23 19:44:49 +00:00
|
|
|
each of the buttons in that panel.
|
|
|
|
|
|
|
|
In its current implementation TrackInfo is not derived from a
|
2014-06-03 20:30:19 +00:00
|
|
|
wxWindow. Following the original coding style, it has
|
|
|
|
been coded as a 'flyweight' class, which is passed
|
2010-01-23 19:44:49 +00:00
|
|
|
state as needed, except for the array of gains and pans.
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
If we'd instead coded it as a wxWindow, we would have an instance
|
|
|
|
of this class for each instance displayed.
|
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackClip
|
|
|
|
\brief One clip (i.e short section) of a WaveTrack.
|
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackPanelListener
|
|
|
|
\brief A now badly named class which is used to give access to a
|
2010-09-12 05:11:43 +00:00
|
|
|
subset of the TrackPanel methods from all over the place.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackList
|
|
|
|
\brief A list of TrackListNode items.
|
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackListIterator
|
|
|
|
\brief An iterator for a TrackList.
|
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackListNode
|
|
|
|
\brief Used by TrackList, points to a Track.
|
|
|
|
|
|
|
|
*//**************************************************************//**
|
|
|
|
|
|
|
|
\class TrackPanel::AudacityTimer
|
2014-06-03 20:30:19 +00:00
|
|
|
\brief Timer class dedicated to infomring the TrackPanel that it
|
2010-01-23 19:44:49 +00:00
|
|
|
is time to refresh some aspect of the screen.
|
|
|
|
|
|
|
|
*//*****************************************************************//**
|
|
|
|
|
|
|
|
\page TrackPanelRefactor Track Panel Refactor
|
|
|
|
\brief Planned refactoring of TrackPanel.cpp
|
|
|
|
|
|
|
|
- Move menus from current TrackPanel into TrackInfo.
|
|
|
|
- Convert TrackInfo from 'flyweight' to heavyweight.
|
|
|
|
- Split GuiStereoTrack and GuiWaveTrack out from TrackPanel.
|
|
|
|
|
|
|
|
JKC: Incremental refactoring started April/2003
|
|
|
|
|
|
|
|
Possibly aiming for Gui classes something like this - it's under
|
|
|
|
discussion:
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
+----------------------------------------------------+
|
|
|
|
| AdornedRulerPanel |
|
|
|
|
+----------------------------------------------------+
|
|
|
|
+----------------------------------------------------+
|
|
|
|
|+------------+ +-----------------------------------+|
|
|
|
|
|| | | (L) GuiWaveTrack ||
|
2014-11-14 03:03:17 +00:00
|
|
|
|| TrackInfo | +-----------------------------------+|
|
2010-01-23 19:44:49 +00:00
|
|
|
|| | +-----------------------------------+|
|
|
|
|
|| | | (R) GuiWaveTrack ||
|
|
|
|
|+------------+ +-----------------------------------+|
|
|
|
|
+-------- GuiStereoTrack ----------------------------+
|
|
|
|
+----------------------------------------------------+
|
|
|
|
|+------------+ +-----------------------------------+|
|
|
|
|
|| | | (L) GuiWaveTrack ||
|
2014-11-14 03:03:17 +00:00
|
|
|
|| TrackInfo | +-----------------------------------+|
|
2010-01-23 19:44:49 +00:00
|
|
|
|| | +-----------------------------------+|
|
|
|
|
|| | | (R) GuiWaveTrack ||
|
|
|
|
|+------------+ +-----------------------------------+|
|
|
|
|
+-------- GuiStereoTrack ----------------------------+
|
|
|
|
</pre>
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
With the whole lot sitting in a TrackPanel which forwards
|
2010-01-23 19:44:49 +00:00
|
|
|
events to the sub objects.
|
|
|
|
|
|
|
|
The GuiStereoTrack class will do the special logic for
|
2014-06-03 20:30:19 +00:00
|
|
|
Stereo channel grouping.
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
The precise names of the classes are subject to revision.
|
2014-06-03 20:30:19 +00:00
|
|
|
Have deliberately not created new files for the new classes
|
2010-01-23 19:44:49 +00:00
|
|
|
such as AdornedRulerPanel and TrackInfo - yet.
|
|
|
|
|
|
|
|
*//*****************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#include "Audacity.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include "TrackPanel.h"
|
|
|
|
|
|
|
|
#include <math.h>
|
2010-04-21 05:03:24 +00:00
|
|
|
#include <stdlib.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//#define DEBUG_DRAW_TIMING 1
|
2014-11-08 14:30:19 +00:00
|
|
|
// #define SPECTRAL_EDITING_ESC_KEY
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include <wx/combobox.h>
|
|
|
|
#include <wx/dcclient.h>
|
|
|
|
#include <wx/dcbuffer.h>
|
|
|
|
#include <wx/dcmemory.h>
|
|
|
|
#include <wx/font.h>
|
|
|
|
#include <wx/fontenum.h>
|
|
|
|
#include <wx/log.h>
|
|
|
|
#include <wx/menu.h>
|
|
|
|
#include <wx/msgdlg.h>
|
|
|
|
#include <wx/textdlg.h>
|
|
|
|
#include <wx/numdlg.h>
|
|
|
|
#include <wx/choicdlg.h>
|
|
|
|
#include <wx/spinctrl.h>
|
|
|
|
#include <wx/listbox.h>
|
|
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <wx/intl.h>
|
|
|
|
#include <wx/image.h>
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#include "FreqWindow.h" // for SpectrumAnalyst
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "AColor.h"
|
|
|
|
#include "AllThemeResources.h"
|
|
|
|
#include "AudacityApp.h"
|
|
|
|
#include "AudioIO.h"
|
|
|
|
#include "Envelope.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include "float_cast.h"
|
|
|
|
#include "Internat.h"
|
|
|
|
#include "LabelTrack.h"
|
2010-07-21 04:53:38 +00:00
|
|
|
#include "Lyrics.h"
|
|
|
|
#include "LyricsWindow.h"
|
|
|
|
#include "MixerBoard.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include "NoteTrack.h"
|
|
|
|
#include "Prefs.h"
|
|
|
|
#include "Project.h"
|
|
|
|
#include "Snap.h"
|
|
|
|
#include "Theme.h"
|
|
|
|
#include "TimeTrack.h"
|
|
|
|
#include "Track.h"
|
|
|
|
#include "TrackArtist.h"
|
|
|
|
#include "TrackPanelAx.h"
|
|
|
|
#include "ViewInfo.h"
|
|
|
|
#include "WaveTrack.h"
|
|
|
|
|
|
|
|
#include "ondemand/ODManager.h"
|
|
|
|
|
|
|
|
#include "toolbars/ControlToolBar.h"
|
|
|
|
#include "toolbars/ToolManager.h"
|
|
|
|
#include "toolbars/ToolsToolBar.h"
|
|
|
|
|
|
|
|
#include "widgets/ASlider.h"
|
|
|
|
#include "widgets/Ruler.h"
|
2014-11-08 15:18:43 +00:00
|
|
|
#include "widgets/NumericTextCtrl.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include <wx/arrimpl.cpp>
|
|
|
|
|
2013-11-28 01:11:09 +00:00
|
|
|
#define ZOOMLIMIT 0.001
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
WX_DEFINE_OBJARRAY(TrackClipArray);
|
|
|
|
|
|
|
|
//This loads the appropriate set of cursors, depending on platform.
|
|
|
|
#include "../images/Cursors.h"
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#define kLeftInset 4
|
|
|
|
#define kTopInset 4
|
|
|
|
#define kTimerInterval 50 // milliseconds
|
|
|
|
|
|
|
|
// Is the distance between A and B less than D?
|
|
|
|
template < class A, class B, class DIST > bool within(A a, B b, DIST d)
|
|
|
|
{
|
|
|
|
return (a > b - d) && (a < b + d);
|
|
|
|
}
|
|
|
|
|
|
|
|
template < class LOW, class MID, class HIGH >
|
|
|
|
bool between_inclusive(LOW l, MID m, HIGH h)
|
|
|
|
{
|
|
|
|
return (m >= l && m <= h);
|
|
|
|
}
|
|
|
|
|
|
|
|
template < class LOW, class MID, class HIGH >
|
|
|
|
bool between_exclusive(LOW l, MID m, HIGH h)
|
|
|
|
{
|
|
|
|
return (m > l && m < h);
|
|
|
|
}
|
|
|
|
|
|
|
|
template < class CLIPPEE, class CLIPVAL >
|
|
|
|
void clip_top(CLIPPEE & clippee, CLIPVAL val)
|
|
|
|
{
|
|
|
|
if (clippee > val)
|
|
|
|
clippee = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
template < class CLIPPEE, class CLIPVAL >
|
|
|
|
void clip_bottom(CLIPPEE & clippee, CLIPVAL val)
|
|
|
|
{
|
|
|
|
if (clippee < val)
|
|
|
|
clippee = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum {
|
|
|
|
TrackPanelFirstID = 2000,
|
|
|
|
OnSetNameID,
|
|
|
|
OnSetFontID,
|
|
|
|
|
|
|
|
OnMoveUpID,
|
|
|
|
OnMoveDownID,
|
2013-11-03 01:17:58 +00:00
|
|
|
OnMoveTopID,
|
|
|
|
OnMoveBottomID,
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
OnUpOctaveID,
|
|
|
|
OnDownOctaveID,
|
|
|
|
|
|
|
|
OnChannelLeftID,
|
|
|
|
OnChannelRightID,
|
|
|
|
OnChannelMonoID,
|
|
|
|
|
|
|
|
OnRate8ID, // <---
|
|
|
|
OnRate11ID, // |
|
|
|
|
OnRate16ID, // |
|
|
|
|
OnRate22ID, // |
|
|
|
|
OnRate44ID, // |
|
|
|
|
OnRate48ID, // | Leave these in order
|
2012-12-03 05:39:17 +00:00
|
|
|
OnRate88ID, // |
|
|
|
|
OnRate96ID, // |
|
|
|
|
OnRate176ID, // | see OnTrackMenu()
|
|
|
|
OnRate192ID, // |
|
|
|
|
OnRate352ID, // |
|
|
|
|
OnRate384ID, // |
|
2010-01-23 19:44:49 +00:00
|
|
|
OnRateOtherID, // |
|
|
|
|
// |
|
|
|
|
On16BitID, // |
|
|
|
|
On24BitID, // |
|
|
|
|
OnFloatID, // <---
|
|
|
|
|
|
|
|
OnWaveformID,
|
|
|
|
OnWaveformDBID,
|
|
|
|
OnSpectrumID,
|
|
|
|
OnSpectrumLogID,
|
|
|
|
OnPitchID,
|
|
|
|
|
|
|
|
OnSplitStereoID,
|
|
|
|
OnSplitStereoMonoID,
|
|
|
|
OnMergeStereoID,
|
2013-11-03 01:17:58 +00:00
|
|
|
OnSwapChannelsID,
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
OnSetTimeTrackRangeID,
|
|
|
|
OnCutSelectedTextID,
|
|
|
|
OnCopySelectedTextID,
|
|
|
|
OnPasteSelectedTextID,
|
2014-07-12 14:26:07 +00:00
|
|
|
OnDeleteSelectedLabelID,
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
OnTimeTrackLinID,
|
|
|
|
OnTimeTrackLogID,
|
|
|
|
OnTimeTrackLogIntID,
|
2010-01-23 19:44:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
BEGIN_EVENT_TABLE(TrackPanel, wxWindow)
|
|
|
|
EVT_MOUSE_EVENTS(TrackPanel::OnMouseEvent)
|
|
|
|
EVT_MOUSE_CAPTURE_LOST(TrackPanel::OnCaptureLost)
|
|
|
|
EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, TrackPanel::OnCaptureKey)
|
|
|
|
EVT_KEY_DOWN(TrackPanel::OnKeyDown)
|
|
|
|
EVT_CHAR(TrackPanel::OnChar)
|
|
|
|
EVT_SIZE(TrackPanel::OnSize)
|
|
|
|
EVT_ERASE_BACKGROUND(TrackPanel::OnErase)
|
|
|
|
EVT_PAINT(TrackPanel::OnPaint)
|
|
|
|
EVT_SET_FOCUS(TrackPanel::OnSetFocus)
|
|
|
|
EVT_KILL_FOCUS(TrackPanel::OnKillFocus)
|
|
|
|
EVT_CONTEXT_MENU(TrackPanel::OnContextMenu)
|
|
|
|
EVT_MENU(OnSetNameID, TrackPanel::OnSetName)
|
|
|
|
EVT_MENU(OnSetFontID, TrackPanel::OnSetFont)
|
|
|
|
EVT_MENU(OnSetTimeTrackRangeID, TrackPanel::OnSetTimeTrackRange)
|
|
|
|
|
|
|
|
EVT_MENU_RANGE(OnMoveUpID, OnMoveDownID, TrackPanel::OnMoveTrack)
|
2013-11-03 01:17:58 +00:00
|
|
|
EVT_MENU_RANGE(OnMoveTopID, OnMoveBottomID, TrackPanel::OnMoveTrack)
|
2010-01-23 19:44:49 +00:00
|
|
|
EVT_MENU_RANGE(OnUpOctaveID, OnDownOctaveID, TrackPanel::OnChangeOctave)
|
|
|
|
EVT_MENU_RANGE(OnChannelLeftID, OnChannelMonoID,
|
|
|
|
TrackPanel::OnChannelChange)
|
|
|
|
EVT_MENU_RANGE(OnWaveformID, OnPitchID, TrackPanel::OnSetDisplay)
|
2012-12-03 05:39:17 +00:00
|
|
|
EVT_MENU_RANGE(OnRate8ID, OnRate384ID, TrackPanel::OnRateChange)
|
2010-01-23 19:44:49 +00:00
|
|
|
EVT_MENU_RANGE(On16BitID, OnFloatID, TrackPanel::OnFormatChange)
|
|
|
|
EVT_MENU(OnRateOtherID, TrackPanel::OnRateOther)
|
2013-11-03 01:17:58 +00:00
|
|
|
EVT_MENU(OnSwapChannelsID, TrackPanel::OnSwapChannels)
|
2010-01-23 19:44:49 +00:00
|
|
|
EVT_MENU(OnSplitStereoID, TrackPanel::OnSplitStereo)
|
|
|
|
EVT_MENU(OnSplitStereoMonoID, TrackPanel::OnSplitStereoMono)
|
|
|
|
EVT_MENU(OnMergeStereoID, TrackPanel::OnMergeStereo)
|
|
|
|
|
|
|
|
EVT_MENU(OnCutSelectedTextID, TrackPanel::OnCutSelectedText)
|
|
|
|
EVT_MENU(OnCopySelectedTextID, TrackPanel::OnCopySelectedText)
|
|
|
|
EVT_MENU(OnPasteSelectedTextID, TrackPanel::OnPasteSelectedText)
|
2014-07-12 14:26:07 +00:00
|
|
|
EVT_MENU(OnDeleteSelectedLabelID, TrackPanel::OnDeleteSelectedLabel)
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
EVT_MENU(OnTimeTrackLinID, TrackPanel::OnTimeTrackLin)
|
|
|
|
EVT_MENU(OnTimeTrackLogID, TrackPanel::OnTimeTrackLog)
|
|
|
|
EVT_MENU(OnTimeTrackLogIntID, TrackPanel::OnTimeTrackLogInt)
|
2010-01-23 19:44:49 +00:00
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
|
|
/// Makes a cursor from an XPM, uses CursorId as a fallback.
|
2013-09-25 22:57:54 +00:00
|
|
|
static wxCursor * MakeCursor( int WXUNUSED(CursorId), const char * pXpm[36], int HotX, int HotY )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxCursor * pCursor;
|
|
|
|
|
|
|
|
#ifdef CURSORS_SIZE32
|
|
|
|
const int HotAdjust =0;
|
|
|
|
#else
|
|
|
|
const int HotAdjust =8;
|
|
|
|
#endif
|
|
|
|
|
2013-10-29 21:49:45 +00:00
|
|
|
wxImage Image = wxImage(wxBitmap(pXpm).ConvertToImage());
|
2010-01-23 19:44:49 +00:00
|
|
|
Image.SetMaskColour(255,0,0);
|
|
|
|
Image.SetMask();// Enable mask.
|
|
|
|
|
2014-10-16 16:18:04 +00:00
|
|
|
#if defined(__WXGTK__) && !wxCHECK_VERSION(3, 0, 0)
|
2010-01-23 19:44:49 +00:00
|
|
|
//
|
|
|
|
// Kludge: the wxCursor Image constructor is broken in wxGTK.
|
|
|
|
// This code, based loosely on the broken code from the wxGTK source,
|
|
|
|
// works around the problem by constructing a 1-bit bitmap and
|
|
|
|
// calling the other custom cursor constructor.
|
|
|
|
//
|
|
|
|
// -DMM
|
|
|
|
//
|
|
|
|
|
|
|
|
unsigned char *rgbBits = Image.GetData();
|
|
|
|
int w = Image.GetWidth() ;
|
|
|
|
int h = Image.GetHeight();
|
|
|
|
int imagebitcount = (w*h)/8;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
unsigned char *bits = new unsigned char [imagebitcount];
|
|
|
|
unsigned char *maskBits = new unsigned char [imagebitcount];
|
|
|
|
|
|
|
|
int i, j, i8;
|
|
|
|
unsigned char cMask;
|
|
|
|
for (i=0; i<imagebitcount; i++) {
|
|
|
|
bits[i] = 0;
|
|
|
|
i8 = i * 8;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
cMask = 1;
|
|
|
|
for (j=0; j<8; j++) {
|
|
|
|
if (rgbBits[(i8+j)*3+2] < 127)
|
|
|
|
bits[i] = bits[i] | cMask;
|
|
|
|
cMask = cMask * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=0; i<imagebitcount; i++) {
|
|
|
|
maskBits[i] = 0x0;
|
|
|
|
i8 = i * 8;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
cMask = 1;
|
|
|
|
for (j=0; j<8; j++) {
|
|
|
|
if (rgbBits[(i8+j)*3] < 127 || rgbBits[(i8+j)*3+1] > 127)
|
|
|
|
maskBits[i] = maskBits[i] | cMask;
|
|
|
|
cMask = cMask * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pCursor = new wxCursor((const char *)bits, w, h,
|
|
|
|
HotX-HotAdjust, HotY-HotAdjust,
|
|
|
|
(const char *)maskBits);
|
|
|
|
|
|
|
|
delete [] bits;
|
|
|
|
delete [] maskBits;
|
|
|
|
|
|
|
|
#else
|
|
|
|
Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_X, HotX-HotAdjust );
|
|
|
|
Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_Y, HotY-HotAdjust );
|
|
|
|
pCursor = new wxCursor( Image );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return pCursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Don't warn us about using 'this' in the base member initializer list.
|
|
|
|
#ifndef __WXGTK__ //Get rid if this pragma for gtk
|
|
|
|
#pragma warning( disable: 4355 )
|
|
|
|
#endif
|
|
|
|
TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
|
|
|
|
const wxPoint & pos,
|
|
|
|
const wxSize & size,
|
|
|
|
TrackList * tracks,
|
|
|
|
ViewInfo * viewInfo,
|
|
|
|
TrackPanelListener * listener,
|
|
|
|
AdornedRulerPanel * ruler)
|
|
|
|
: wxPanel(parent, id, pos, size, wxWANTS_CHARS | wxNO_BORDER),
|
|
|
|
mTrackInfo(this),
|
|
|
|
mListener(listener),
|
|
|
|
mTracks(tracks),
|
|
|
|
mViewInfo(viewInfo),
|
|
|
|
mRuler(ruler),
|
|
|
|
mTrackArtist(NULL),
|
|
|
|
mBacking(NULL),
|
|
|
|
mRefreshBacking(false),
|
2014-11-08 15:18:43 +00:00
|
|
|
mConverter(NumericConverter::TIME),
|
2010-01-23 19:44:49 +00:00
|
|
|
mAutoScrolling(false),
|
2010-02-02 19:43:52 +00:00
|
|
|
mVertScrollRemainder(0),
|
|
|
|
vrulerSize(36,0)
|
2010-01-23 19:44:49 +00:00
|
|
|
#ifndef __WXGTK__ //Get rid if this pragma for gtk
|
|
|
|
#pragma warning( default: 4355 )
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
SetLabel(_("Track Panel"));
|
|
|
|
SetName(_("Track Panel"));
|
|
|
|
|
|
|
|
mAx = new TrackPanelAx( this );
|
|
|
|
#if wxUSE_ACCESSIBILITY
|
|
|
|
SetAccessible( mAx );
|
|
|
|
#endif
|
|
|
|
mMouseCapture = IsUncaptured;
|
|
|
|
mSlideUpDownOnly = false;
|
|
|
|
mLabelTrackStartXPos=-1;
|
|
|
|
mCircularTrackNavigation = false;
|
|
|
|
|
|
|
|
UpdatePrefs();
|
|
|
|
|
|
|
|
mRedrawAfterStop = false;
|
|
|
|
mIndicatorShowing = false;
|
|
|
|
|
|
|
|
mPencilCursor = MakeCursor( wxCURSOR_PENCIL, DrawCursorXpm, 12, 22);
|
|
|
|
mSelectCursor = MakeCursor( wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
|
|
|
|
mEnvelopeCursor= MakeCursor( wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
|
|
|
|
mDisabledCursor= MakeCursor( wxCURSOR_NO_ENTRY, DisabledCursorXpm,16, 16);
|
|
|
|
mSlideCursor = MakeCursor( wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
|
|
|
|
mZoomInCursor = MakeCursor( wxCURSOR_MAGNIFIER, ZoomInCursorXpm, 19, 15);
|
|
|
|
mZoomOutCursor = MakeCursor( wxCURSOR_MAGNIFIER, ZoomOutCursorXpm, 19, 15);
|
|
|
|
mLabelCursorLeft = MakeCursor( wxCURSOR_ARROW, LabelCursorLeftXpm, 19, 15);
|
|
|
|
mLabelCursorRight = MakeCursor( wxCURSOR_ARROW, LabelCursorRightXpm, 16, 16);
|
2014-11-08 14:30:19 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
mBottomFrequencyCursor =
|
|
|
|
MakeCursor( wxCURSOR_ARROW, BottomFrequencyCursorXpm, 16, 16);
|
|
|
|
mTopFrequencyCursor =
|
|
|
|
MakeCursor( wxCURSOR_ARROW, TopFrequencyCursorXpm, 16, 16);
|
|
|
|
mBandWidthCursor = MakeCursor( wxCURSOR_ARROW, BandWidthCursorXpm, 16, 16);
|
|
|
|
#endif
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#if USE_MIDI
|
|
|
|
mStretchMode = stretchCenter;
|
|
|
|
mStretching = false;
|
|
|
|
mStretched = false;
|
|
|
|
mStretchStart = 0;
|
|
|
|
mStretchCursor = MakeCursor( wxCURSOR_BULLSEYE, StretchCursorXpm, 16, 16);
|
2014-06-03 20:30:19 +00:00
|
|
|
mStretchLeftCursor = MakeCursor( wxCURSOR_BULLSEYE,
|
2010-09-18 21:02:36 +00:00
|
|
|
StretchLeftCursorXpm, 16, 16);
|
2014-06-03 20:30:19 +00:00
|
|
|
mStretchRightCursor = MakeCursor( wxCURSOR_BULLSEYE,
|
2010-09-18 21:02:36 +00:00
|
|
|
StretchRightCursorXpm, 16, 16);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mArrowCursor = new wxCursor(wxCURSOR_ARROW);
|
|
|
|
mSmoothCursor = new wxCursor(wxCURSOR_SPRAYCAN);
|
|
|
|
mResizeCursor = new wxCursor(wxCURSOR_SIZENS);
|
|
|
|
mRearrangeCursor = new wxCursor(wxCURSOR_HAND);
|
|
|
|
mAdjustLeftSelectionCursor = new wxCursor(wxCURSOR_POINT_LEFT);
|
|
|
|
mAdjustRightSelectionCursor = new wxCursor(wxCURSOR_POINT_RIGHT);
|
|
|
|
|
|
|
|
mWaveTrackMenu = NULL;
|
|
|
|
mNoteTrackMenu = NULL;
|
|
|
|
mLabelTrackMenu = NULL;
|
|
|
|
mLabelTrackInfoMenu = NULL;
|
|
|
|
mTimeTrackMenu = NULL;
|
|
|
|
|
|
|
|
BuildMenus();
|
|
|
|
|
|
|
|
mTrackArtist = new TrackArtist();
|
|
|
|
mTrackArtist->SetInset(1, kTopInset + 1, kLeftInset + 2, 2);
|
|
|
|
|
|
|
|
mCapturedTrack = NULL;
|
|
|
|
mPopupMenuTarget = NULL;
|
|
|
|
|
|
|
|
mTimeCount = 0;
|
|
|
|
mTimer.parent = this;
|
|
|
|
mTimer.Start(kTimerInterval, FALSE);
|
|
|
|
|
|
|
|
//Initialize a member variable pointing to the current
|
|
|
|
//drawing track.
|
|
|
|
mDrawingTrack =NULL;
|
|
|
|
|
|
|
|
//More initializations, some of these for no other reason than
|
|
|
|
//to prevent runtime memory check warnings
|
|
|
|
mZoomStart = -1;
|
|
|
|
mZoomEnd = -1;
|
|
|
|
mPrevWidth = -1;
|
|
|
|
mPrevHeight = -1;
|
|
|
|
|
|
|
|
//Initialize the last selection adjustment time.
|
|
|
|
mLastSelectionAdjustment = ::wxGetLocalTimeMillis();
|
|
|
|
|
|
|
|
// This is used to snap the cursor to the nearest track that
|
|
|
|
// lines up with it.
|
|
|
|
mSnapManager = NULL;
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
|
|
|
|
mLastCursor = -1;
|
|
|
|
mLastIndicator = -1;
|
|
|
|
|
|
|
|
// Register for tracklist updates
|
|
|
|
mTracks->Connect(EVT_TRACKLIST_RESIZED,
|
|
|
|
wxCommandEventHandler(TrackPanel::OnTrackListResized),
|
|
|
|
NULL,
|
|
|
|
this);
|
|
|
|
mTracks->Connect(EVT_TRACKLIST_UPDATED,
|
|
|
|
wxCommandEventHandler(TrackPanel::OnTrackListUpdated),
|
|
|
|
NULL,
|
|
|
|
this);
|
2014-10-18 14:19:38 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
mFrequencySnapper.reset(new SpectrumAnalyst());
|
2014-11-29 16:53:28 +00:00
|
|
|
|
|
|
|
mLastF0 = mLastF1 = SelectedRegion::UndefinedFrequency;
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
2014-11-29 17:10:56 +00:00
|
|
|
|
|
|
|
mSelStartValid = false;
|
|
|
|
mSelStart = 0;
|
2014-11-29 22:09:57 +00:00
|
|
|
|
2015-04-12 16:39:11 +00:00
|
|
|
mInitialTrackSelection = new std::vector<bool>;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TrackPanel::~TrackPanel()
|
|
|
|
{
|
|
|
|
mTimer.Stop();
|
|
|
|
|
|
|
|
// Unregister for tracklist updates
|
|
|
|
mTracks->Disconnect(EVT_TRACKLIST_UPDATED,
|
|
|
|
wxCommandEventHandler(TrackPanel::OnTrackListUpdated),
|
|
|
|
NULL,
|
|
|
|
this);
|
|
|
|
mTracks->Disconnect(EVT_TRACKLIST_RESIZED,
|
|
|
|
wxCommandEventHandler(TrackPanel::OnTrackListResized),
|
|
|
|
NULL,
|
|
|
|
this);
|
|
|
|
|
|
|
|
// This can happen if a label is being edited and the user presses
|
|
|
|
// ALT+F4 or Command+Q
|
|
|
|
if (HasCapture())
|
|
|
|
ReleaseMouse();
|
|
|
|
|
|
|
|
if (mBacking)
|
|
|
|
{
|
|
|
|
mBackingDC.SelectObject( wxNullBitmap );
|
|
|
|
delete mBacking;
|
|
|
|
}
|
|
|
|
delete mTrackArtist;
|
|
|
|
|
|
|
|
delete mArrowCursor;
|
|
|
|
delete mPencilCursor;
|
|
|
|
delete mSelectCursor;
|
|
|
|
delete mEnvelopeCursor;
|
|
|
|
delete mDisabledCursor;
|
|
|
|
delete mSlideCursor;
|
|
|
|
delete mResizeCursor;
|
|
|
|
delete mSmoothCursor;
|
|
|
|
delete mZoomInCursor;
|
|
|
|
delete mZoomOutCursor;
|
|
|
|
delete mLabelCursorLeft;
|
|
|
|
delete mLabelCursorRight;
|
|
|
|
delete mRearrangeCursor;
|
|
|
|
delete mAdjustLeftSelectionCursor;
|
|
|
|
delete mAdjustRightSelectionCursor;
|
2014-11-08 14:30:19 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
delete mBottomFrequencyCursor;
|
|
|
|
delete mTopFrequencyCursor;
|
|
|
|
delete mBandWidthCursor;
|
|
|
|
#endif
|
2011-10-19 23:06:53 +00:00
|
|
|
#if USE_MIDI
|
|
|
|
delete mStretchCursor;
|
|
|
|
delete mStretchLeftCursor;
|
|
|
|
delete mStretchRightCursor;
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
delete mSnapManager;
|
|
|
|
|
|
|
|
DeleteMenus();
|
|
|
|
|
|
|
|
#if !wxUSE_ACCESSIBILITY
|
|
|
|
delete mAx;
|
|
|
|
#endif
|
2015-04-12 16:39:11 +00:00
|
|
|
|
|
|
|
delete mInitialTrackSelection;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::BuildMenus(void)
|
|
|
|
{
|
|
|
|
// Get rid of existing menus
|
|
|
|
DeleteMenus();
|
|
|
|
|
|
|
|
// Use AppendCheckItem so we can have ticks beside the items.
|
|
|
|
// We would use AppendRadioItem but it only currently works on windows and GTK.
|
|
|
|
mRateMenu = new wxMenu();
|
2014-12-14 16:01:21 +00:00
|
|
|
mRateMenu->AppendRadioItem(OnRate8ID, wxT("8000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate11ID, wxT("11025 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate16ID, wxT("16000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate22ID, wxT("22050 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate44ID, wxT("44100 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate48ID, wxT("48000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate88ID, wxT("88200 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate96ID, wxT("96000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate176ID, wxT("176400 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate192ID, wxT("192000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate352ID, wxT("352800 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRate384ID, wxT("384000 Hz"));
|
|
|
|
mRateMenu->AppendRadioItem(OnRateOtherID, _("&Other..."));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mFormatMenu = new wxMenu();
|
2014-12-14 16:01:21 +00:00
|
|
|
mFormatMenu->AppendRadioItem(On16BitID, GetSampleFormatStr(int16Sample));
|
|
|
|
mFormatMenu->AppendRadioItem(On24BitID, GetSampleFormatStr(int24Sample));
|
|
|
|
mFormatMenu->AppendRadioItem(OnFloatID, GetSampleFormatStr(floatSample));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-10-30 22:07:06 +00:00
|
|
|
/* build the pop-down menu used on wave (sampled audio) tracks */
|
2010-01-23 19:44:49 +00:00
|
|
|
mWaveTrackMenu = new wxMenu();
|
2014-11-08 14:30:19 +00:00
|
|
|
BuildCommonDropMenuItems(mWaveTrackMenu); // does name, up/down etc
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(OnWaveformID, _("Wa&veform"));
|
2013-11-03 01:17:58 +00:00
|
|
|
mWaveTrackMenu->Append(OnWaveformDBID, _("&Waveform (dB)"));
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(OnSpectrumID, _("&Spectrogram"));
|
2012-03-20 16:48:57 +00:00
|
|
|
/* i18n-hint: short form of 'logarithm'*/
|
2014-06-03 20:30:19 +00:00
|
|
|
mWaveTrackMenu->Append(OnSpectrumLogID, _("Spectrogram l&og(f)"));
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(OnPitchID, _("Pitc&h (EAC)"));
|
2010-01-23 19:44:49 +00:00
|
|
|
mWaveTrackMenu->AppendSeparator();
|
2014-12-14 16:01:21 +00:00
|
|
|
mWaveTrackMenu->AppendRadioItem(OnChannelMonoID, _("&Mono"));
|
|
|
|
mWaveTrackMenu->AppendRadioItem(OnChannelLeftID, _("&Left Channel"));
|
|
|
|
mWaveTrackMenu->AppendRadioItem(OnChannelRightID, _("&Right Channel"));
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(OnMergeStereoID, _("Ma&ke Stereo Track"));
|
2013-11-03 01:17:58 +00:00
|
|
|
mWaveTrackMenu->Append(OnSwapChannelsID, _("Swap Stereo &Channels"));
|
|
|
|
mWaveTrackMenu->Append(OnSplitStereoID, _("Spl&it Stereo Track"));
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(OnSplitStereoMonoID, _("Split Stereo to Mo&no"));
|
2010-01-23 19:44:49 +00:00
|
|
|
mWaveTrackMenu->AppendSeparator();
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(0, _("Set Sample &Format"), mFormatMenu);
|
2010-01-23 19:44:49 +00:00
|
|
|
mWaveTrackMenu->AppendSeparator();
|
2012-05-10 09:09:49 +00:00
|
|
|
mWaveTrackMenu->Append(0, _("Set Rat&e"), mRateMenu);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-10-30 22:07:06 +00:00
|
|
|
/* build the pop-down menu used on note (MIDI) tracks */
|
2010-01-23 19:44:49 +00:00
|
|
|
mNoteTrackMenu = new wxMenu();
|
2014-11-08 14:30:19 +00:00
|
|
|
BuildCommonDropMenuItems(mNoteTrackMenu); // does name, up/down etc
|
2012-05-10 09:09:49 +00:00
|
|
|
mNoteTrackMenu->Append(OnUpOctaveID, _("Up &Octave"));
|
|
|
|
mNoteTrackMenu->Append(OnDownOctaveID, _("Down Octa&ve"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-10-30 22:07:06 +00:00
|
|
|
/* build the pop-down menu used on label tracks */
|
2010-01-23 19:44:49 +00:00
|
|
|
mLabelTrackMenu = new wxMenu();
|
2014-11-08 14:30:19 +00:00
|
|
|
BuildCommonDropMenuItems(mLabelTrackMenu); // does name, up/down etc
|
2012-05-10 09:09:49 +00:00
|
|
|
mLabelTrackMenu->Append(OnSetFontID, _("&Font..."));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-10-30 22:07:06 +00:00
|
|
|
/* build the pop-down menu used on time warping tracks */
|
2010-01-23 19:44:49 +00:00
|
|
|
mTimeTrackMenu = new wxMenu();
|
2014-11-08 14:30:19 +00:00
|
|
|
BuildCommonDropMenuItems(mTimeTrackMenu); // does name, up/down etc
|
2012-12-19 21:49:25 +00:00
|
|
|
mTimeTrackMenu->Append(OnTimeTrackLinID, _("&Linear"));
|
|
|
|
mTimeTrackMenu->Append(OnTimeTrackLogID, _("L&ogarithmic"));
|
|
|
|
mTimeTrackMenu->AppendSeparator();
|
2012-05-10 09:09:49 +00:00
|
|
|
mTimeTrackMenu->Append(OnSetTimeTrackRangeID, _("Set Ra&nge..."));
|
2012-12-19 21:49:25 +00:00
|
|
|
mTimeTrackMenu->AppendCheckItem(OnTimeTrackLogIntID, _("Logarithmic &Interpolation"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mLabelTrackInfoMenu = new wxMenu();
|
2014-07-12 14:26:07 +00:00
|
|
|
mLabelTrackInfoMenu->Append(OnCutSelectedTextID, _("Cu&t"));
|
|
|
|
mLabelTrackInfoMenu->Append(OnCopySelectedTextID, _("&Copy"));
|
|
|
|
mLabelTrackInfoMenu->Append(OnPasteSelectedTextID, _("&Paste"));
|
|
|
|
mLabelTrackInfoMenu->Append(OnDeleteSelectedLabelID, _("&Delete Label"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-10-30 22:07:06 +00:00
|
|
|
void TrackPanel::BuildCommonDropMenuItems(wxMenu * menu)
|
|
|
|
{
|
|
|
|
menu->Append(OnSetNameID, _("N&ame..."));
|
|
|
|
menu->AppendSeparator();
|
2013-11-03 01:17:58 +00:00
|
|
|
menu->Append(OnMoveUpID, _("Move Track &Up"));
|
2013-10-30 22:07:06 +00:00
|
|
|
menu->Append(OnMoveDownID, _("Move Track &Down"));
|
2013-11-03 01:17:58 +00:00
|
|
|
menu->Append(OnMoveTopID, _("Move Track to &Top"));
|
|
|
|
menu->Append(OnMoveBottomID, _("Move Track to &Bottom"));
|
2013-10-30 22:07:06 +00:00
|
|
|
menu->AppendSeparator();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::DeleteMenus(void)
|
|
|
|
{
|
|
|
|
// Note that the submenus (mRateMenu, ...)
|
|
|
|
// are deleted by their parent
|
|
|
|
if (mWaveTrackMenu) {
|
|
|
|
delete mWaveTrackMenu;
|
|
|
|
mWaveTrackMenu = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mNoteTrackMenu) {
|
|
|
|
delete mNoteTrackMenu;
|
|
|
|
mNoteTrackMenu = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mLabelTrackMenu) {
|
|
|
|
delete mLabelTrackMenu;
|
|
|
|
mLabelTrackMenu = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mLabelTrackInfoMenu) {
|
|
|
|
delete mLabelTrackInfoMenu;
|
|
|
|
mLabelTrackInfoMenu = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTimeTrackMenu) {
|
|
|
|
delete mTimeTrackMenu;
|
|
|
|
mTimeTrackMenu = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
void TrackPanel::UpdateVirtualStereoOrder()
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *t;
|
|
|
|
int temp;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
for (t = iter.First(); t; t = iter.Next()) {
|
|
|
|
if(t->GetKind() == Track::Wave && t->GetChannel() == Track::MonoChannel){
|
|
|
|
WaveTrack *wt = (WaveTrack*)t;
|
|
|
|
|
|
|
|
if(WaveTrack::mMonoAsVirtualStereo && wt->GetPan() != 0){
|
|
|
|
temp = wt->GetHeight();
|
|
|
|
wt->SetHeight(temp*wt->GetVirtualTrackPercentage());
|
|
|
|
wt->SetHeight(temp - wt->GetHeight(),true);
|
|
|
|
}else if(!WaveTrack::mMonoAsVirtualStereo && wt->GetPan() != 0){
|
|
|
|
wt->SetHeight(wt->GetHeight() + wt->GetHeight(true));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t = iter.First();
|
|
|
|
if(t){
|
|
|
|
t->ReorderList(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::UpdatePrefs()
|
|
|
|
{
|
|
|
|
mdBr = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);
|
|
|
|
gPrefs->Read(wxT("/GUI/AutoScroll"), &mViewInfo->bUpdateTrackIndicator,
|
|
|
|
true);
|
|
|
|
gPrefs->Read(wxT("/GUI/AdjustSelectionEdges"), &mAdjustSelectionEdges,
|
|
|
|
true);
|
|
|
|
gPrefs->Read(wxT("/GUI/CircularTrackNavigation"), &mCircularTrackNavigation,
|
|
|
|
false);
|
|
|
|
gPrefs->Read(wxT("/GUI/Solo"), &mSoloPref, wxT("Standard") );
|
|
|
|
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekShort,
|
|
|
|
1.0);
|
|
|
|
gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekLong,
|
|
|
|
15.0);
|
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
bool temp = WaveTrack::mMonoAsVirtualStereo;
|
|
|
|
gPrefs->Read(wxT("/GUI/MonoAsVirtualStereo"), &WaveTrack::mMonoAsVirtualStereo,
|
|
|
|
false);
|
|
|
|
|
|
|
|
if(WaveTrack::mMonoAsVirtualStereo != temp)
|
|
|
|
UpdateVirtualStereoOrder();
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mTrackArtist) {
|
|
|
|
mTrackArtist->UpdatePrefs();
|
|
|
|
}
|
|
|
|
|
|
|
|
// All vertical rulers must be recalculated since the minimum and maximum
|
|
|
|
// frequences may have been changed.
|
|
|
|
UpdateVRulers();
|
|
|
|
Refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::SetStop(bool bStopped)
|
|
|
|
{
|
|
|
|
mViewInfo->bIsPlaying = !bStopped;
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Remembers the track we clicked on and why we captured it.
|
2014-06-03 20:30:19 +00:00
|
|
|
/// We also use this method to clear the record
|
2010-01-23 19:44:49 +00:00
|
|
|
/// of the captured track, passing NULL as the track.
|
|
|
|
void TrackPanel::SetCapturedTrack( Track * t, enum MouseCaptureEnum MouseCapture )
|
|
|
|
{
|
|
|
|
#if defined(__WXDEBUG__)
|
|
|
|
if (t == NULL) {
|
|
|
|
wxASSERT(MouseCapture == IsUncaptured);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
wxASSERT(MouseCapture != IsUncaptured);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mCapturedTrack = t;
|
|
|
|
mMouseCapture = MouseCapture;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::SelectNone()
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *t = iter.First();
|
|
|
|
while (t) {
|
|
|
|
t->SetSelected(false);
|
|
|
|
if (t->GetKind() == Track::Label)
|
|
|
|
((LabelTrack *) t)->Unselect();
|
|
|
|
t = iter.Next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Select all tracks marked by the label track lt
|
|
|
|
void TrackPanel::SelectTracksByLabel( LabelTrack *lt )
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *t = iter.First();
|
|
|
|
|
|
|
|
//do nothing if at least one other track is selected
|
|
|
|
while (t) {
|
|
|
|
if( t->GetSelected() && t != lt )
|
|
|
|
return;
|
|
|
|
t = iter.Next();
|
|
|
|
}
|
|
|
|
|
|
|
|
//otherwise, select all tracks
|
|
|
|
t = iter.First();
|
|
|
|
while( t )
|
|
|
|
{
|
|
|
|
t->SetSelected( true );
|
|
|
|
t = iter.Next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-18 06:30:07 +00:00
|
|
|
// Set selection length to the length of a track -- but if sync-lock is turned
|
2014-06-03 20:30:19 +00:00
|
|
|
// on, use the largest possible selection in the sync-lock group.
|
2011-04-18 06:30:07 +00:00
|
|
|
// If it's a stereo track, do the same for the stereo channels.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::SelectTrackLength(Track *t)
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetActiveProject();
|
2010-08-11 23:56:50 +00:00
|
|
|
SyncLockedTracksIterator it(mTracks);
|
2010-01-23 19:44:49 +00:00
|
|
|
Track *t1 = it.First(t);
|
|
|
|
double minOffset = t->GetOffset();
|
|
|
|
double maxEnd = t->GetEndTime();
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// If we have a sync-lock group and sync-lock linking is on,
|
2011-04-26 21:19:59 +00:00
|
|
|
// check the sync-lock group tracks.
|
2010-08-11 22:47:26 +00:00
|
|
|
if (p->IsSyncLocked() && t1 != NULL)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
for ( ; t1; t1 = it.Next())
|
|
|
|
{
|
|
|
|
if (t1->GetOffset() < minOffset)
|
|
|
|
minOffset = t1->GetOffset();
|
|
|
|
if (t1->GetEndTime() > maxEnd)
|
|
|
|
maxEnd = t1->GetEndTime();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Otherwise, check for a stereo pair
|
|
|
|
t1 = t->GetLink();
|
|
|
|
if (t1)
|
|
|
|
{
|
|
|
|
if (t1->GetOffset() < minOffset)
|
|
|
|
minOffset = t1->GetOffset();
|
|
|
|
if (t1->GetEndTime() > maxEnd)
|
|
|
|
maxEnd = t1->GetEndTime();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
// PRL: double click or click on track control.
|
|
|
|
// should this select all frequencies too? I think not.
|
|
|
|
mViewInfo->selectedRegion.setTimes(minOffset, maxEnd);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::GetTracksUsableArea(int *width, int *height) const
|
|
|
|
{
|
|
|
|
GetSize(width, height);
|
|
|
|
*width -= GetLabelWidth();
|
|
|
|
// AS: MAGIC NUMBER: What does 2 represent?
|
|
|
|
*width -= 2 + kLeftInset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the pointer to the AudacityProject that
|
|
|
|
/// goes with this track panel.
|
|
|
|
AudacityProject * TrackPanel::GetProject() const
|
|
|
|
{
|
|
|
|
//JKC casting away constness here.
|
|
|
|
//Do it in two stages in case 'this' is not a wxWindow.
|
|
|
|
//when the compiler will flag the error.
|
|
|
|
wxWindow const * const pConstWind = this;
|
|
|
|
wxWindow * pWind=(wxWindow*)pConstWind;
|
|
|
|
#ifdef EXPERIMENTAL_NOTEBOOK
|
|
|
|
pWind = pWind->GetParent(); //Page
|
|
|
|
wxASSERT( pWind );
|
|
|
|
pWind = pWind->GetParent(); //Notebook
|
|
|
|
wxASSERT( pWind );
|
|
|
|
#endif
|
|
|
|
pWind = pWind->GetParent(); //MainPanel
|
|
|
|
wxASSERT( pWind );
|
|
|
|
pWind = pWind->GetParent(); //Project
|
|
|
|
wxASSERT( pWind );
|
|
|
|
return (AudacityProject*)pWind;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// AS: This gets called on our wx timer events.
|
|
|
|
void TrackPanel::OnTimer()
|
|
|
|
{
|
|
|
|
mTimeCount++;
|
|
|
|
// AS: If the user is dragging the mouse and there is a track that
|
|
|
|
// has captured the mouse, then scroll the screen, as necessary.
|
|
|
|
if ((mMouseCapture==IsSelecting) && mCapturedTrack) {
|
|
|
|
ScrollDuringDrag();
|
|
|
|
}
|
|
|
|
|
|
|
|
wxCommandEvent dummyEvent;
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
if ((p->GetAudioIOToken() > 0) &&
|
2015-04-14 18:52:22 +00:00
|
|
|
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
|
2010-07-21 04:53:38 +00:00
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
// Update lyrics display.
|
2010-07-21 04:53:38 +00:00
|
|
|
LyricsWindow* pLyricsWindow = p->GetLyricsWindow();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pLyricsWindow)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-07-21 04:53:38 +00:00
|
|
|
Lyrics* pLyricsPanel = pLyricsWindow->GetLyricsPanel();
|
|
|
|
pLyricsPanel->Update(gAudioIO->GetStreamTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-07-21 04:53:38 +00:00
|
|
|
}
|
|
|
|
|
2011-02-26 23:19:19 +00:00
|
|
|
//v Vaughan, 2011-02-25: Moved this update back here from audacityAudioCallback.
|
2014-06-03 20:30:19 +00:00
|
|
|
// See note there.
|
|
|
|
// Vaughan, 2010-01-30:
|
|
|
|
// Since all we're doing here is updating the meters, I moved it to
|
2011-01-31 01:49:01 +00:00
|
|
|
// audacityAudioCallback where it calls gAudioIO->mOutputMeter->UpdateDisplay().
|
2011-02-26 01:50:49 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pMixerBoard &&
|
2015-04-14 18:52:22 +00:00
|
|
|
(p->GetAudioIOToken() > 0) &&
|
|
|
|
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
|
2011-02-26 01:50:49 +00:00
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
pMixerBoard->UpdateMeters(gAudioIO->GetStreamTime(),
|
2015-04-14 18:52:22 +00:00
|
|
|
(p->mLastPlayMode == loopedPlay));
|
2011-02-26 01:50:49 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Check whether we were playing or recording, but the stream has stopped.
|
|
|
|
if (p->GetAudioIOToken()>0 &&
|
2014-06-03 20:30:19 +00:00
|
|
|
!gAudioIO->IsStreamActive(p->GetAudioIOToken()))
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
//the stream may have been started up after this one finished (by some other project)
|
|
|
|
//in that case reset the buttons don't stop the stream
|
|
|
|
p->GetControlToolBar()->StopPlaying(!gAudioIO->IsStreamActive());
|
2010-07-21 04:53:38 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Reset lyrics display.
|
2010-07-21 04:53:38 +00:00
|
|
|
LyricsWindow* pLyricsWindow = p->GetLyricsWindow();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pLyricsWindow)
|
2010-07-21 04:53:38 +00:00
|
|
|
{
|
|
|
|
Lyrics* pLyricsPanel = pLyricsWindow->GetLyricsPanel();
|
|
|
|
pLyricsPanel->Update(p->GetSel0());
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Vaughan, 2011-01-28: No longer doing this on timer.
|
2011-01-31 01:49:01 +00:00
|
|
|
// Now it's in AudioIO::SetMeters() and AudioIO::StopStream(), as with Meter Toolbar meters.
|
2014-06-03 20:30:19 +00:00
|
|
|
//if (pMixerBoard)
|
2011-01-31 01:49:01 +00:00
|
|
|
// pMixerBoard->ResetMeters(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Next, check to see if we were playing or recording
|
|
|
|
// audio, but now Audio I/O is completely finished.
|
|
|
|
if (p->GetAudioIOToken()>0 &&
|
2014-06-03 20:30:19 +00:00
|
|
|
!gAudioIO->IsAudioTokenActive(p->GetAudioIOToken()))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-12-29 22:28:32 +00:00
|
|
|
p->FixScrollbars();
|
2014-12-28 08:53:57 +00:00
|
|
|
p->SetAudioIOToken(0);
|
2014-12-29 22:28:32 +00:00
|
|
|
p->RedrawProject();
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mRedrawAfterStop = false;
|
|
|
|
|
2013-02-20 23:42:58 +00:00
|
|
|
//ANSWER-ME: Was DisplaySelection added to solve a repaint problem?
|
2010-01-23 19:44:49 +00:00
|
|
|
DisplaySelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
// AS: The "indicator" is the little graphical mark shown in the ruler
|
2010-09-18 21:02:36 +00:00
|
|
|
// that indicates where the current play/record position is. (This also
|
|
|
|
// draws the moving vertical line.)
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!gAudioIO->IsPaused() &&
|
|
|
|
( mIndicatorShowing || gAudioIO->IsStreamActive(p->GetAudioIOToken())))
|
|
|
|
{
|
|
|
|
DrawIndicator();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(gAudioIO->IsStreamActive(p->GetAudioIOToken()) &&
|
|
|
|
gAudioIO->GetNumCaptureChannels()) {
|
|
|
|
|
|
|
|
// Periodically update the display while recording
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!mRedrawAfterStop) {
|
|
|
|
mRedrawAfterStop = true;
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
mListener->TP_ScrollUpDown( 99999999 );
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ((mTimeCount % 5) == 0) {
|
|
|
|
// Must tell OnPaint() to recreate the backing bitmap
|
|
|
|
// since we've not done a full refresh.
|
|
|
|
mRefreshBacking = true;
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(mTimeCount > 1000)
|
|
|
|
mTimeCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// We check on each timer tick to see if we need to scroll.
|
|
|
|
/// Scrolling is handled by mListener, which is an interface
|
|
|
|
/// to the window TrackPanel is embedded in.
|
|
|
|
void TrackPanel::ScrollDuringDrag()
|
|
|
|
{
|
|
|
|
// DM: If we're "autoscrolling" (which means that we're scrolling
|
|
|
|
// because the user dragged from inside to outside the window,
|
|
|
|
// not because the user clicked in the scroll bar), then
|
|
|
|
// the selection code needs to be handled slightly differently.
|
|
|
|
// We set this flag ("mAutoScrolling") to tell the selecting
|
|
|
|
// code that we didn't get here as a result of a mouse event,
|
|
|
|
// and therefore it should ignore the mouseEvent parameter,
|
|
|
|
// and instead use the last known mouse position. Setting
|
|
|
|
// this flag also causes the Mac to redraw immediately rather
|
|
|
|
// than waiting for the next update event; this makes scrolling
|
|
|
|
// smoother on MacOS 9.
|
|
|
|
|
|
|
|
if (mMouseMostRecentX >= mCapturedRect.x + mCapturedRect.width) {
|
|
|
|
mAutoScrolling = true;
|
|
|
|
mListener->TP_ScrollRight();
|
|
|
|
}
|
|
|
|
else if (mMouseMostRecentX < mCapturedRect.x) {
|
|
|
|
mAutoScrolling = true;
|
|
|
|
mListener->TP_ScrollLeft();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mAutoScrolling) {
|
|
|
|
// AS: To keep the selection working properly as we scroll,
|
2010-09-12 05:11:43 +00:00
|
|
|
// we fake a mouse event (remember, this method is called
|
2010-01-23 19:44:49 +00:00
|
|
|
// from a timer tick).
|
|
|
|
|
|
|
|
// AS: For some reason, GCC won't let us pass this directly.
|
|
|
|
wxMouseEvent e(wxEVT_MOTION);
|
|
|
|
HandleSelect(e);
|
|
|
|
mAutoScrolling = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This updates the indicator (on a timer tick) that shows
|
|
|
|
/// where the current play or record position is. To do this,
|
|
|
|
/// we cheat a little. The indicator is drawn during the ruler
|
|
|
|
/// drawing process (that should probably change, but...), so
|
|
|
|
/// we create a memory DC and tell the ruler to draw itself there,
|
|
|
|
/// and then just blit that to the screen.
|
|
|
|
/// The indicator is a small triangle, red for record, green for play.
|
|
|
|
void TrackPanel::DrawIndicator()
|
|
|
|
{
|
|
|
|
wxClientDC dc( this );
|
|
|
|
DoDrawIndicator( dc );
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Second level DrawIndicator()
|
2011-06-04 02:41:16 +00:00
|
|
|
void TrackPanel::DoDrawIndicator(wxDC & dc, bool repairOld /* = false */)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool onScreen;
|
|
|
|
int x;
|
2011-06-04 02:41:16 +00:00
|
|
|
double pos;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
if (!repairOld)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-06-04 02:41:16 +00:00
|
|
|
// Erase the old indicator.
|
|
|
|
if( mLastIndicator != -1 )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-06-04 02:41:16 +00:00
|
|
|
onScreen = between_inclusive( mViewInfo->h,
|
|
|
|
mLastIndicator,
|
|
|
|
mViewInfo->h + mViewInfo->screen );
|
|
|
|
if( onScreen )
|
|
|
|
{
|
|
|
|
x = GetLeftOffset() + int ( ( mLastIndicator - mViewInfo->h) * mViewInfo->zoom );
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// LL: Keep from trying to blit outsize of the source DC. This results in a crash on
|
|
|
|
// OSX due to allocating memory using negative sizes and can be caused by resizing
|
|
|
|
// the project window while recording or playing.
|
|
|
|
int w = dc.GetSize().GetWidth();
|
|
|
|
if (x >= w) {
|
|
|
|
x = w - 1;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
dc.Blit( x, 0, 1, mBacking->GetHeight(), &mBackingDC, x, 0 );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// Nothing's ever perfect...
|
|
|
|
//
|
|
|
|
// Redraw the cursor since we may have just wiped it out
|
|
|
|
if( mLastCursor == mLastIndicator )
|
|
|
|
{
|
|
|
|
DoDrawCursor( dc );
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
mLastIndicator = -1;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// The stream time can be < 0 if the audio is currently stopped
|
|
|
|
pos = gAudioIO->GetStreamTime();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
bool audioActive = ( gAudioIO->IsStreamActive( p->GetAudioIOToken() ) != 0 );
|
|
|
|
onScreen = between_inclusive( mViewInfo->h,
|
|
|
|
pos,
|
|
|
|
mViewInfo->h + mViewInfo->screen );
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// This displays the audio time, too...
|
|
|
|
DisplaySelection();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// BG: Scroll screen if option is set
|
|
|
|
// msmeyer: But only if not playing looped or in one-second mode
|
|
|
|
if( mViewInfo->bUpdateTrackIndicator &&
|
|
|
|
p->mLastPlayMode != loopedPlay &&
|
2014-06-03 20:30:19 +00:00
|
|
|
p->mLastPlayMode != oneSecondPlay &&
|
2011-06-04 02:41:16 +00:00
|
|
|
audioActive &&
|
|
|
|
pos >= 0 &&
|
|
|
|
!onScreen &&
|
|
|
|
!gAudioIO->IsPaused() )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-06-04 02:41:16 +00:00
|
|
|
mListener->TP_ScrollWindow( pos );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// Always update scrollbars even if not scrolling the window. This is
|
|
|
|
// important when new audio is recorded, because this can change the
|
|
|
|
// length of the project and therefore the appearance of the scrollbar.
|
|
|
|
MakeParentRedrawScrollbars();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
mIndicatorShowing = ( onScreen && audioActive );
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
// Remember it
|
|
|
|
mLastIndicator = pos;
|
|
|
|
} else {
|
|
|
|
pos = mLastIndicator;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Set play/record color
|
|
|
|
bool rec = (gAudioIO->GetNumCaptureChannels() > 0);
|
|
|
|
AColor::IndicatorColor( &dc, !rec);
|
|
|
|
|
|
|
|
// Calculate the horizontal position of the indicator
|
|
|
|
x = GetLeftOffset() + int ( ( pos - mViewInfo->h ) * mViewInfo->zoom );
|
|
|
|
|
|
|
|
mRuler->DrawIndicator( pos, rec );
|
|
|
|
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
// Ensure that we don't draw through the TrackInfo or vertical ruler.
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect clip = GetRect();
|
|
|
|
int leftCutoff = clip.x + GetLabelWidth();
|
|
|
|
int rightInset = kLeftInset + 2; // See the call to SetInset
|
|
|
|
int rightCutoff = clip.x + clip.width - rightInset;
|
|
|
|
if (!between_inclusive(leftCutoff, x, rightCutoff))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw indicator in all visible tracks
|
|
|
|
VisibleTrackIterator iter( GetProject() );
|
|
|
|
for( Track *t = iter.First(); t; t = iter.Next() )
|
|
|
|
{
|
|
|
|
// Don't draw the indicator in label tracks
|
|
|
|
if( t->GetKind() == Track::Label )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert virtual coordinate to physical
|
|
|
|
int y = t->GetY() - mViewInfo->vpos;
|
|
|
|
|
|
|
|
// Draw the new indicator in its new location
|
|
|
|
AColor::Line(dc,
|
|
|
|
x,
|
|
|
|
y + kTopInset + 1,
|
|
|
|
x,
|
|
|
|
y + t->GetHeight() - 3 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// This method draws the cursor things, both in the
|
2010-01-23 19:44:49 +00:00
|
|
|
/// ruler as seen at the top of the screen, but also in each of the
|
|
|
|
/// selected tracks.
|
2010-10-28 17:34:35 +00:00
|
|
|
/// These are the 'vertical lines' through waves, notes, and ruler.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::DrawCursor()
|
|
|
|
{
|
|
|
|
wxClientDC dc( this );
|
|
|
|
DoDrawCursor( dc );
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Second level DrawCursor()
|
|
|
|
void TrackPanel::DoDrawCursor(wxDC & dc)
|
|
|
|
{
|
|
|
|
bool onScreen;
|
|
|
|
int x;
|
|
|
|
|
|
|
|
if( mLastCursor != -1 )
|
|
|
|
{
|
|
|
|
onScreen = between_inclusive( mViewInfo->h,
|
|
|
|
mLastCursor,
|
|
|
|
mViewInfo->h + mViewInfo->screen );
|
|
|
|
if( onScreen )
|
|
|
|
{
|
|
|
|
x = GetLeftOffset() + int ( ( mLastCursor - mViewInfo->h) * mViewInfo->zoom );
|
|
|
|
|
|
|
|
dc.Blit( x, 0, 1, mBacking->GetHeight(), &mBackingDC, x, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
mLastCursor = -1;
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
mLastCursor = mViewInfo->selectedRegion.t0();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
onScreen = between_inclusive( mViewInfo->h,
|
|
|
|
mLastCursor,
|
|
|
|
mViewInfo->h + mViewInfo->screen );
|
|
|
|
|
|
|
|
if( !onScreen )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AColor::CursorColor( &dc );
|
|
|
|
|
|
|
|
x = GetLeftOffset() +
|
|
|
|
int ( ( mLastCursor - mViewInfo->h ) * mViewInfo->zoom );
|
|
|
|
|
|
|
|
// Draw cursor in all selected tracks
|
|
|
|
VisibleTrackIterator iter( GetProject() );
|
|
|
|
for( Track *t = iter.First(); t; t = iter.Next() )
|
|
|
|
{
|
|
|
|
if( t->GetSelected() || mAx->IsFocused( t ) )
|
|
|
|
{
|
|
|
|
int y = t->GetY() - mViewInfo->vpos + 1;
|
|
|
|
wxCoord top = y + kTopInset;
|
|
|
|
wxCoord bottom = y + t->GetHeight() - kTopInset;
|
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
// MB: warp() is not needed here as far as I know, in fact it creates a bug. Removing it fixes that.
|
|
|
|
AColor::Line( dc, x, top, x, bottom ); // <-- The whole point of this routine.
|
2013-05-30 23:14:25 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(MONO_WAVE_PAN(t)){
|
|
|
|
y = t->GetY(true) - mViewInfo->vpos + 1;
|
|
|
|
top = y + kTopInset;
|
|
|
|
bottom = y + t->GetHeight(true) - kTopInset;
|
|
|
|
AColor::Line( dc, x, top, x, bottom );
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AS: Ah, no, this is where we draw the blinky thing in the ruler.
|
|
|
|
mRuler->DrawCursor( mLastCursor );
|
|
|
|
|
|
|
|
DisplaySelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// OnSize() is called when the panel is resized
|
|
|
|
void TrackPanel::OnSize(wxSizeEvent & /* event */)
|
|
|
|
{
|
|
|
|
int width, height;
|
|
|
|
GetSize( &width, &height );
|
|
|
|
|
|
|
|
// wxMac doesn't like zero dimensions, so protect against it
|
|
|
|
width = width == 0 ? 1 : width;
|
|
|
|
height = height == 0 ? 1 : height;
|
|
|
|
|
|
|
|
// (Re)allocate the backing bitmap
|
|
|
|
if( mBacking )
|
|
|
|
{
|
|
|
|
mBackingDC.SelectObject( wxNullBitmap );
|
|
|
|
delete mBacking;
|
|
|
|
}
|
|
|
|
|
|
|
|
mBacking = new wxBitmap( width, height );
|
|
|
|
mBackingDC.SelectObject( *mBacking );
|
|
|
|
|
|
|
|
// Refresh the entire area. Really only need to refresh when
|
|
|
|
// expanding...is it worth the trouble?
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// OnErase( ) is called during the normal course of
|
2010-01-23 19:44:49 +00:00
|
|
|
/// completing an erase operation.
|
|
|
|
void TrackPanel::OnErase(wxEraseEvent & /* event */)
|
|
|
|
{
|
|
|
|
// Ignore it for now. This reduces flashing when dragging windows
|
|
|
|
// over track area while playing or recording.
|
|
|
|
//
|
|
|
|
// However, if artifacts or the like are discovered later, then
|
|
|
|
// we could blit the backing bitmap here.
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// AS: OnPaint( ) is called during the normal course of
|
2010-01-23 19:44:49 +00:00
|
|
|
/// completing a repaint operation.
|
|
|
|
void TrackPanel::OnPaint(wxPaintEvent & /* event */)
|
|
|
|
{
|
|
|
|
#if DEBUG_DRAW_TIMING
|
|
|
|
wxStopWatch sw;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Construct the paint DC on the heap so that it may be deleted
|
|
|
|
// early
|
|
|
|
wxDC *dc = new wxPaintDC( this );
|
|
|
|
|
|
|
|
// Retrieve the damage rectangle
|
|
|
|
wxRect box = GetUpdateRegion().GetBox();
|
|
|
|
|
|
|
|
// Recreate the backing bitmap if we have a full refresh
|
|
|
|
// (See TrackPanel::Refresh())
|
|
|
|
if( mRefreshBacking || ( box == GetRect() ) )
|
|
|
|
{
|
2010-04-21 05:03:24 +00:00
|
|
|
// Update visible sliders
|
|
|
|
mTrackInfo.UpdateSliderOffset(mViewInfo->track);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Reset (should a mutex be used???)
|
|
|
|
mRefreshBacking = false;
|
|
|
|
|
|
|
|
// Redraw the backing bitmap
|
2015-04-14 18:52:22 +00:00
|
|
|
DrawTracks(&mBackingDC);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Copy it to the display
|
2015-04-14 18:52:22 +00:00
|
|
|
dc->Blit(0, 0, mBacking->GetWidth(), mBacking->GetHeight(), &mBackingDC, 0, 0);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Copy full, possibly clipped, damage rectange
|
2015-04-14 18:52:22 +00:00
|
|
|
dc->Blit(box.x, box.y, box.width, box.height, &mBackingDC, box.x, box.y);
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Done with the clipped DC
|
|
|
|
delete dc;
|
|
|
|
|
|
|
|
// Drawing now goes directly to the client area
|
2015-04-14 18:52:22 +00:00
|
|
|
wxClientDC cdc(this);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Update the indicator in case it was damaged if this project is playing
|
2015-04-14 18:52:22 +00:00
|
|
|
|
|
|
|
// PRL: mIndicatorShowing never becomes true!
|
2010-01-23 19:44:49 +00:00
|
|
|
AudacityProject* p = GetProject();
|
|
|
|
if (!gAudioIO->IsPaused() &&
|
2015-04-14 18:52:22 +00:00
|
|
|
(mIndicatorShowing || gAudioIO->IsStreamActive(p->GetAudioIOToken())))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-06-04 02:41:16 +00:00
|
|
|
// We just want to repair, not update the old, so set the second param to true.
|
|
|
|
// This is important because this onPaint could be for just some of the tracks.
|
|
|
|
DoDrawIndicator( cdc, true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the cursor
|
2015-04-14 18:52:22 +00:00
|
|
|
if (mViewInfo->selectedRegion.isPoint())
|
|
|
|
DoDrawCursor(cdc);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if DEBUG_DRAW_TIMING
|
|
|
|
sw.Pause();
|
2014-11-08 16:42:34 +00:00
|
|
|
wxLogDebug(wxT("Total: %ld milliseconds"), sw.Time());
|
|
|
|
wxPrintf(wxT("Total: %ld milliseconds\n"), sw.Time());
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Makes our Parent (well, whoever is listening to us) push their state.
|
|
|
|
/// this causes application state to be preserved on a stack for undo ops.
|
|
|
|
void TrackPanel::MakeParentPushState(wxString desc, wxString shortDesc,
|
2011-05-22 13:41:01 +00:00
|
|
|
int flags)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-05-22 13:41:01 +00:00
|
|
|
mListener->TP_PushState(desc, shortDesc, flags);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-12-30 00:41:18 +00:00
|
|
|
void TrackPanel::MakeParentModifyState(bool bWantsAutoSave)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2013-12-30 00:41:18 +00:00
|
|
|
mListener->TP_ModifyState(bWantsAutoSave);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::MakeParentRedrawScrollbars()
|
|
|
|
{
|
|
|
|
mListener->TP_RedrawScrollbars();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::MakeParentResize()
|
|
|
|
{
|
|
|
|
mListener->TP_HandleResize();
|
|
|
|
}
|
|
|
|
|
2015-04-09 19:41:32 +00:00
|
|
|
void TrackPanel::HandleEscapeKey()
|
|
|
|
{
|
|
|
|
switch (mMouseCapture)
|
|
|
|
{
|
2015-04-12 16:39:11 +00:00
|
|
|
case IsSelecting:
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
std::vector<bool>::const_iterator
|
|
|
|
it = mInitialTrackSelection->begin(),
|
|
|
|
end = mInitialTrackSelection->end();
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
|
|
|
wxASSERT(it != end);
|
|
|
|
t->SetSelected(*it++);
|
|
|
|
}
|
|
|
|
mViewInfo->selectedRegion = mInitialSelection;
|
|
|
|
}
|
|
|
|
break;
|
2015-04-09 19:41:32 +00:00
|
|
|
case IsZooming:
|
|
|
|
case IsVZooming:
|
2015-04-12 03:45:46 +00:00
|
|
|
break;
|
|
|
|
case IsResizing:
|
|
|
|
mCapturedTrack->SetHeight(mInitialActualHeight);
|
|
|
|
mCapturedTrack->SetMinimized(mInitialMinimized);
|
|
|
|
break;
|
|
|
|
case IsResizingBetweenLinkedTracks:
|
|
|
|
{
|
|
|
|
Track *const next = mTracks->GetNext(mCapturedTrack);
|
|
|
|
mCapturedTrack->SetHeight(mInitialUpperActualHeight);
|
|
|
|
mCapturedTrack->SetMinimized(mInitialMinimized);
|
|
|
|
next->SetHeight(mInitialActualHeight);
|
|
|
|
next->SetMinimized(mInitialMinimized);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case IsResizingBelowLinkedTracks:
|
|
|
|
{
|
|
|
|
Track *const prev = mTracks->GetPrev(mCapturedTrack);
|
|
|
|
mCapturedTrack->SetHeight(mInitialActualHeight);
|
|
|
|
mCapturedTrack->SetMinimized(mInitialMinimized);
|
|
|
|
prev->SetHeight(mInitialUpperActualHeight);
|
|
|
|
prev->SetMinimized(mInitialMinimized);
|
|
|
|
}
|
|
|
|
break;
|
2015-04-09 19:41:32 +00:00
|
|
|
default:
|
|
|
|
return;
|
2015-04-12 03:45:46 +00:00
|
|
|
;
|
2015-04-09 19:41:32 +00:00
|
|
|
}
|
2015-04-12 03:45:46 +00:00
|
|
|
|
|
|
|
// Common part in all cases that do anything
|
|
|
|
SetCapturedTrack(NULL, IsUncaptured);
|
|
|
|
if (HasCapture())
|
|
|
|
ReleaseMouse();
|
|
|
|
wxMouseEvent dummy;
|
|
|
|
HandleCursor(dummy);
|
|
|
|
Refresh(false);
|
2015-04-09 19:41:32 +00:00
|
|
|
}
|
|
|
|
|
2013-08-09 01:03:34 +00:00
|
|
|
void TrackPanel::HandleAltKey(bool down)
|
|
|
|
{
|
|
|
|
mLastMouseEvent.m_altDown = down;
|
|
|
|
HandleCursorForLastMouseEvent();
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::HandleShiftKey(bool down)
|
|
|
|
{
|
|
|
|
mLastMouseEvent.m_shiftDown = down;
|
|
|
|
HandleCursorForLastMouseEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleControlKey(bool down)
|
|
|
|
{
|
|
|
|
mLastMouseEvent.m_controlDown = down;
|
|
|
|
HandleCursorForLastMouseEvent();
|
|
|
|
}
|
|
|
|
|
2012-02-14 00:18:29 +00:00
|
|
|
void TrackPanel::HandlePageUpKey()
|
|
|
|
{
|
|
|
|
mListener->TP_ScrollWindow(mViewInfo->h + mViewInfo->screen);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandlePageDownKey()
|
|
|
|
{
|
|
|
|
mListener->TP_ScrollWindow(mViewInfo->h - mViewInfo->screen);
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::HandleCursorForLastMouseEvent()
|
|
|
|
{
|
|
|
|
HandleCursor(mLastMouseEvent);
|
|
|
|
}
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
MixerBoard* TrackPanel::GetMixerBoard()
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
wxASSERT(p);
|
|
|
|
return p->GetMixerBoard();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
/// Used to determine whether it is safe or not to perform certain
|
|
|
|
/// edits at the moment.
|
|
|
|
/// @return true if audio is being recorded or is playing.
|
|
|
|
bool TrackPanel::IsUnsafe()
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
bool bUnsafe = (p->GetAudioIOToken()>0 &&
|
|
|
|
gAudioIO->IsStreamActive(p->GetAudioIOToken()));
|
|
|
|
return bUnsafe;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// Uses a previously noted 'activity' to determine what
|
2010-01-23 19:44:49 +00:00
|
|
|
/// cursor to use.
|
|
|
|
/// @var mMouseCapture holds the current activity.
|
|
|
|
bool TrackPanel::SetCursorByActivity( )
|
|
|
|
{
|
|
|
|
bool unsafe = IsUnsafe();
|
|
|
|
|
|
|
|
switch( mMouseCapture )
|
|
|
|
{
|
|
|
|
case IsSelecting:
|
|
|
|
SetCursor(*mSelectCursor);
|
|
|
|
return true;
|
|
|
|
case IsSliding:
|
|
|
|
SetCursor( unsafe ? *mDisabledCursor : *mSlideCursor);
|
|
|
|
return true;
|
|
|
|
case IsEnveloping:
|
|
|
|
SetCursor( unsafe ? *mDisabledCursor : *mEnvelopeCursor);
|
|
|
|
return true;
|
|
|
|
case IsRearranging:
|
|
|
|
SetCursor( unsafe ? *mDisabledCursor : *mRearrangeCursor);
|
|
|
|
return true;
|
|
|
|
case IsAdjustingLabel:
|
|
|
|
return true;
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
case IsStretching:
|
|
|
|
SetCursor( unsafe ? *mDisabledCursor : *mStretchCursor);
|
|
|
|
return true;
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
case IsOverCutLine:
|
|
|
|
SetCursor( unsafe ? *mDisabledCursor : *mArrowCursor);
|
2014-10-18 14:19:38 +00:00
|
|
|
// what, no return true here? -- PRL
|
2010-01-23 19:44:49 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
/// When in the "label" (TrackInfo or vertical ruler), we can either vertical zoom or re-order tracks.
|
2010-01-23 19:44:49 +00:00
|
|
|
/// Dont't change cursor/tip to zoom if display is not waveform (either linear of dB) or Spectrum
|
|
|
|
void TrackPanel::SetCursorAndTipWhenInLabel( Track * t,
|
|
|
|
wxMouseEvent &event, const wxChar ** ppTip )
|
|
|
|
{
|
|
|
|
if (event.m_x >= GetVRulerOffset() &&
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
(t->GetKind() == Track::Wave) &&
|
|
|
|
(((WaveTrack *) t)->GetDisplay() <= WaveTrack::SpectrumDisplay ||
|
2014-06-03 20:30:19 +00:00
|
|
|
((WaveTrack *) t)->GetDisplay() <= WaveTrack::SpectrumLogDisplay))
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
{
|
|
|
|
*ppTip = _("Click to vertically zoom in. Shift-click to zoom out. Drag to specify a zoom region.");
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCursor(event.ShiftDown()? *mZoomOutCursor : *mZoomInCursor);
|
|
|
|
}
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
else if (event.m_x >= GetVRulerOffset() && t->GetKind() == Track::Note) {
|
|
|
|
*ppTip = _("Click to verticaly zoom in, Shift-click to zoom out, Drag to create a particular zoom region.");
|
|
|
|
SetCursor(event.ShiftDown() ? *mZoomOutCursor : *mZoomInCursor);
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
else {
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
// Set a status message if over TrackInfo.
|
|
|
|
*ppTip = _("Drag the track vertically to change the order of the tracks.");
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCursor(*mArrowCursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// When in the resize area we can adjust size or relative size.
|
2014-06-03 20:30:19 +00:00
|
|
|
void TrackPanel::SetCursorAndTipWhenInVResizeArea( Track * label,
|
2010-01-23 19:44:49 +00:00
|
|
|
bool bLinked, const wxChar ** ppTip )
|
|
|
|
{
|
|
|
|
// Check to see whether it is the first channel of a stereo track
|
|
|
|
if (bLinked) {
|
2014-06-03 20:30:19 +00:00
|
|
|
// If we are in the label we got here 'by mistake' and we're
|
|
|
|
// not actually in the resize area at all. (The resize area
|
2010-01-23 19:44:49 +00:00
|
|
|
// is shorter when it is between stereo tracks).
|
|
|
|
|
|
|
|
// IF we are in the label THEN return.
|
2014-06-03 20:30:19 +00:00
|
|
|
// Subsequently called methods can detect that a tip and
|
2010-01-23 19:44:49 +00:00
|
|
|
// cursor are still needed.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (label)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
*ppTip = _("Click and drag to adjust relative size of stereo tracks.");
|
|
|
|
SetCursor(*mResizeCursor);
|
|
|
|
} else {
|
|
|
|
*ppTip = _("Click and drag to resize the track.");
|
|
|
|
SetCursor(*mResizeCursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// When in a label track, find out if we've hit anything that
|
2010-01-23 19:44:49 +00:00
|
|
|
/// would cause a cursor change.
|
2014-06-03 20:30:19 +00:00
|
|
|
void TrackPanel::SetCursorAndTipWhenInLabelTrack( LabelTrack * pLT,
|
2010-01-23 19:44:49 +00:00
|
|
|
wxMouseEvent & event, const wxChar ** ppTip )
|
|
|
|
{
|
|
|
|
int edge=pLT->OverGlyph(event.m_x, event.m_y);
|
|
|
|
if(edge !=0)
|
|
|
|
{
|
|
|
|
SetCursor(*mArrowCursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
//KLUDGE: We refresh the whole Label track when the icon hovered over
|
2014-06-03 20:30:19 +00:00
|
|
|
//changes colouration. As well as being inefficient we are also
|
2010-01-23 19:44:49 +00:00
|
|
|
//doing stuff that should be delegated to the label track itself.
|
2014-06-03 20:30:19 +00:00
|
|
|
edge += pLT->mbHitCenter ? 4:0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if( edge != pLT->mOldEdge )
|
|
|
|
{
|
|
|
|
pLT->mOldEdge = edge;
|
|
|
|
RefreshTrack( pLT );
|
|
|
|
}
|
|
|
|
// IF edge!=0 THEN we've set the cursor and we're done.
|
|
|
|
// signal this by setting the tip.
|
|
|
|
if( edge != 0 )
|
|
|
|
{
|
2014-10-25 22:05:45 +00:00
|
|
|
*ppTip =
|
2010-01-23 19:44:49 +00:00
|
|
|
(pLT->mbHitCenter ) ?
|
2014-10-25 22:05:45 +00:00
|
|
|
_("Drag one or more label boundaries.") :
|
|
|
|
_("Drag label boundary.");
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
inline bool isSpectrogramTrack(const Track *pTrack, bool *pLogf = NULL) {
|
|
|
|
if (pTrack &&
|
|
|
|
pTrack->GetKind() == Track::Wave) {
|
|
|
|
const int display =
|
|
|
|
static_cast<const WaveTrack*>(pTrack)->GetDisplay();
|
|
|
|
const bool logF = (display == WaveTrack::SpectrumLogDisplay);
|
|
|
|
if (pLogf)
|
|
|
|
*pLogf = logF;
|
|
|
|
return logF || (display == WaveTrack::SpectrumDisplay);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (pLogf)
|
|
|
|
*pLogf = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
2014-10-18 14:19:38 +00:00
|
|
|
|
2014-10-26 10:11:35 +00:00
|
|
|
// If we're in OnDemand mode, we may change the tip.
|
|
|
|
void TrackPanel::MaySetOnDemandTip( Track * t, const wxChar ** ppTip )
|
|
|
|
{
|
|
|
|
wxASSERT( t );
|
|
|
|
//For OD regions, we need to override and display the percent complete for this task.
|
|
|
|
//first, make sure it's a wavetrack.
|
|
|
|
if(t->GetKind() != Track::Wave)
|
|
|
|
return;
|
|
|
|
//see if the wavetrack exists in the ODManager (if the ODManager exists)
|
|
|
|
if(!ODManager::IsInstanceCreated())
|
|
|
|
return;
|
|
|
|
//ask the wavetrack for the corresponding tip - it may not change **pptip, but that's fine.
|
|
|
|
ODManager::Instance()->FillTipForWaveTrack((WaveTrack*)t,ppTip);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-08 14:30:19 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
void TrackPanel::HandleCenterFrequencyCursor
|
|
|
|
(bool shiftDown, const wxChar ** ppTip, const wxCursor ** ppCursor)
|
|
|
|
{
|
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
*ppTip =
|
|
|
|
shiftDown ?
|
|
|
|
_("Click and drag to move center selection frequency.") :
|
|
|
|
_("Click and drag to move center selection frequency to a spectral peak.");
|
|
|
|
|
|
|
|
#else
|
|
|
|
shiftDown;
|
|
|
|
|
|
|
|
*ppTip =
|
|
|
|
_("Click and drag to move center selection frequency.");
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
*ppCursor = mEnvelopeCursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleCenterFrequencyClick
|
|
|
|
(bool shiftDown, Track *pTrack, double value)
|
|
|
|
{
|
|
|
|
if (shiftDown) {
|
|
|
|
// Disable time selection
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = false;
|
2014-11-08 14:30:19 +00:00
|
|
|
mFreqSelTrack = static_cast<WaveTrack*>(pTrack);
|
|
|
|
mFreqSelPin = value;
|
|
|
|
mFreqSelMode = FREQ_SEL_DRAG_CENTER;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
// Start center snapping
|
|
|
|
WaveTrack *wt = static_cast<WaveTrack*>(pTrack);
|
|
|
|
// Turn center snapping on (the only way to do this)
|
|
|
|
mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
|
|
|
|
// Disable time selection
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = false;
|
2014-11-08 14:30:19 +00:00
|
|
|
StartSnappingFreqSelection(wt);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// The select tool can have different cursors and prompts depending on what
|
|
|
|
// we hover over, most notably when hovering over the selction boundaries.
|
|
|
|
// Determine and set the cursor and tip accordingly.
|
2014-06-03 20:30:19 +00:00
|
|
|
void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
|
2014-11-08 14:30:19 +00:00
|
|
|
wxMouseEvent & event, wxRect &r, bool bMultiToolMode,
|
|
|
|
const wxChar ** ppTip, const wxCursor ** ppCursor )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-08 14:30:19 +00:00
|
|
|
// Do not set the default cursor here and re-set later, that causes
|
|
|
|
// flashing.
|
|
|
|
*ppCursor = mSelectCursor;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//In Multi-tool mode, give multitool prompt if no-special-hit.
|
|
|
|
if( bMultiToolMode ) {
|
2014-10-25 22:05:45 +00:00
|
|
|
// Look up the current key binding for Preferences.
|
|
|
|
// (Don't assume it's the default!)
|
|
|
|
wxString keyStr
|
|
|
|
(GetProject()->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
|
2014-10-26 10:11:35 +00:00
|
|
|
// Must compose a string that survives the function call, hence static.
|
2014-11-29 22:09:57 +00:00
|
|
|
if (keyStr.IsEmpty())
|
|
|
|
// No keyboard preference defined for opening Preferences dialog
|
|
|
|
/* i18n-hint: These are the names of a menu and a command in that menu */
|
|
|
|
keyStr = _("Edit, Preferences...");
|
2014-10-25 22:05:45 +00:00
|
|
|
static wxString result;
|
2014-11-29 22:09:57 +00:00
|
|
|
/* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
|
2014-10-25 22:05:45 +00:00
|
|
|
result = wxString::Format(
|
|
|
|
_("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
|
|
|
|
keyStr.c_str());
|
|
|
|
*ppTip = result;
|
2014-10-26 10:11:35 +00:00
|
|
|
// Later in this function we may point to some other string instead.
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-10-26 10:11:35 +00:00
|
|
|
// Not over a track? Get out of here.
|
|
|
|
if(!t)
|
|
|
|
return;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Make sure we are within the selected track
|
2014-10-26 10:11:35 +00:00
|
|
|
// Adjusting the selection edges can be turned off in
|
|
|
|
// the preferences...
|
|
|
|
if ( !t->GetSelected() || !mAdjustSelectionEdges)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-10-26 10:11:35 +00:00
|
|
|
MaySetOnDemandTip( t, ppTip );
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-10-05 17:10:09 +00:00
|
|
|
wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x);
|
|
|
|
wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Something is wrong if right edge comes before left edge
|
|
|
|
wxASSERT(!(rightSel < leftSel));
|
|
|
|
|
2014-10-26 10:11:35 +00:00
|
|
|
const bool bShiftDown = event.ShiftDown();
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-26 10:11:35 +00:00
|
|
|
bool logF;
|
2014-11-08 14:30:19 +00:00
|
|
|
if ( (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
|
2014-10-26 10:11:35 +00:00
|
|
|
isSpectrogramTrack(t, &logF) ) {
|
|
|
|
// Not shift-down, but center frequency snapping toggle is on
|
2014-11-08 14:30:19 +00:00
|
|
|
*ppTip = _("Click and drag to set frequency bandwidth.");
|
|
|
|
*ppCursor = mEnvelopeCursor;
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2014-10-26 13:35:17 +00:00
|
|
|
|
|
|
|
// If not shift-down and not snapping center, then
|
2014-10-26 10:11:35 +00:00
|
|
|
// choose boundaries only in snapping tolerance,
|
2014-10-26 13:35:17 +00:00
|
|
|
// and may choose center
|
|
|
|
SelectionBoundary boundary = ChooseBoundary(event, t, r, !bShiftDown, !bShiftDown);
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef USE_MIDI
|
2014-10-26 10:11:35 +00:00
|
|
|
// The MIDI HitTest will only succeed if we are on a midi track, so
|
|
|
|
// typically we will fall through.
|
|
|
|
switch( boundary) {
|
|
|
|
case SBNone:
|
|
|
|
case SBLeft:
|
|
|
|
case SBRight:
|
|
|
|
if ( HitTestStretch(t, r, event)) {
|
|
|
|
*ppTip = _("Click and drag to stretch within selected region.");
|
2014-11-08 14:30:19 +00:00
|
|
|
*ppCursor = mStretchCursor;
|
2014-10-25 22:05:45 +00:00
|
|
|
return;
|
2014-10-26 10:11:35 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
2014-10-26 10:11:35 +00:00
|
|
|
|
|
|
|
switch (boundary) {
|
|
|
|
case SBNone:
|
|
|
|
if( bShiftDown ){
|
2014-10-26 13:35:17 +00:00
|
|
|
// wxASSERT( false );
|
2014-10-26 10:11:35 +00:00
|
|
|
// Same message is used for moving left right top or bottom edge.
|
|
|
|
*ppTip = _("Click to move selection boundary to cursor.");
|
|
|
|
// No cursor change.
|
2014-10-25 22:05:45 +00:00
|
|
|
return;
|
2014-10-26 10:11:35 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SBLeft:
|
|
|
|
*ppTip = _("Click and drag to move left selection boundary.");
|
2014-11-08 14:30:19 +00:00
|
|
|
*ppCursor = mAdjustLeftSelectionCursor;
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
|
|
|
case SBRight:
|
|
|
|
*ppTip = _("Click and drag to move right selection boundary.");
|
2014-11-08 14:30:19 +00:00
|
|
|
*ppCursor = mAdjustRightSelectionCursor;
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-26 10:11:35 +00:00
|
|
|
case SBBottom:
|
2014-11-11 15:24:22 +00:00
|
|
|
*ppTip = _("Click and drag to move bottom selection frequency.");
|
|
|
|
*ppCursor = mBottomFrequencyCursor;
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
|
|
|
case SBTop:
|
2014-11-11 15:24:22 +00:00
|
|
|
*ppTip = _("Click and drag to move top selection frequency.");
|
|
|
|
*ppCursor = mTopFrequencyCursor;
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
|
|
|
case SBCenter:
|
2014-11-08 14:30:19 +00:00
|
|
|
HandleCenterFrequencyCursor(bShiftDown, ppTip, ppCursor);
|
2014-10-26 10:11:35 +00:00
|
|
|
return;
|
2014-11-11 15:24:22 +00:00
|
|
|
case SBWidth:
|
|
|
|
*ppTip = _("Click and drag to adjust frequency bandwidth.");
|
|
|
|
*ppCursor = mBandWidthCursor;
|
|
|
|
return;
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2014-10-26 10:11:35 +00:00
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
} // switch
|
|
|
|
// Falls through the switch if there was no boundary found.
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-10-26 10:11:35 +00:00
|
|
|
MaySetOnDemandTip( t, ppTip );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// In this method we know what tool we are using,
|
2010-01-23 19:44:49 +00:00
|
|
|
/// so set the cursor accordingly.
|
2014-06-03 20:30:19 +00:00
|
|
|
void TrackPanel::SetCursorAndTipByTool( int tool,
|
2010-01-23 19:44:49 +00:00
|
|
|
wxMouseEvent & event, const wxChar ** /*ppTip*/ )
|
|
|
|
{
|
|
|
|
bool unsafe = IsUnsafe();
|
|
|
|
|
|
|
|
// Change the cursor based on the active tool.
|
|
|
|
switch (tool) {
|
|
|
|
case selectTool:
|
2014-06-03 20:30:19 +00:00
|
|
|
wxFAIL;// should have already been handled
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case envelopeTool:
|
|
|
|
SetCursor(unsafe ? *mDisabledCursor : *mEnvelopeCursor);
|
|
|
|
break;
|
|
|
|
case slideTool:
|
|
|
|
SetCursor(unsafe ? *mDisabledCursor : *mSlideCursor);
|
|
|
|
break;
|
|
|
|
case zoomTool:
|
|
|
|
SetCursor(event.ShiftDown()? *mZoomOutCursor : *mZoomInCursor);
|
|
|
|
break;
|
|
|
|
case drawTool:
|
|
|
|
if (unsafe)
|
|
|
|
SetCursor(*mDisabledCursor);
|
|
|
|
else
|
|
|
|
SetCursor(event.AltDown()? *mSmoothCursor : *mPencilCursor);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// doesn't actually change the tip itself, but it could (should?) do at some
|
|
|
|
// future date.
|
|
|
|
}
|
|
|
|
|
|
|
|
/// TrackPanel::HandleCursor( ) sets the cursor drawn at the mouse location.
|
|
|
|
/// As this procedure checks which region the mouse is over, it is
|
|
|
|
/// appropriate to establish the message in the status bar.
|
|
|
|
void TrackPanel::HandleCursor(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
mLastMouseEvent = event;
|
|
|
|
|
|
|
|
// (1), If possible, set the cursor based on the current activity
|
|
|
|
// ( leave the StatusBar alone ).
|
|
|
|
if( SetCursorByActivity() )
|
|
|
|
return;
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// (2) If we are not over a track at all, set the cursor to Arrow and
|
|
|
|
// clear the StatusBar,
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect r;
|
|
|
|
Track *label = FindTrack(event.m_x, event.m_y, true, true, &r);
|
|
|
|
Track *nonlabel = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
Track *t = label ? label : nonlabel;
|
|
|
|
|
|
|
|
if (!t) {
|
|
|
|
SetCursor(*mArrowCursor);
|
|
|
|
mListener->TP_DisplayStatusMessage(wxT(""));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// (3) The easy cases are done.
|
|
|
|
// Now we've got to hit-test against a number of different possibilities.
|
|
|
|
// We could be over the label or a vertical ruler etc...
|
|
|
|
|
|
|
|
// Strategy here is to set the tip when we have determined and
|
|
|
|
// set the correct cursor. We stop doing tests for what we've
|
|
|
|
// hit once the tip is not NULL.
|
|
|
|
const wxChar *tip = NULL;
|
|
|
|
|
|
|
|
if (label) {
|
|
|
|
SetCursorAndTipWhenInLabel( label, event, &tip );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Are we within the vertical resize area?
|
2014-06-03 20:30:19 +00:00
|
|
|
if ((tip == NULL ) && within(event.m_y, r.y + r.height, TRACK_RESIZE_REGION))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SetCursorAndTipWhenInVResizeArea( label, t->GetLinked(), &tip );
|
|
|
|
// tip may still be NULL at this point, in which case we go on looking.
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Otherwise, we must be over a track of some kind
|
2010-01-23 19:44:49 +00:00
|
|
|
// Is it a label track?
|
|
|
|
if ((tip==NULL) && (t->GetKind() == Track::Label))
|
|
|
|
{
|
|
|
|
// We are over a label track
|
|
|
|
SetCursorAndTipWhenInLabelTrack( (LabelTrack*)t, event, &tip );
|
2014-06-03 20:30:19 +00:00
|
|
|
// ..and if we haven't yet determined the cursor,
|
2010-01-23 19:44:49 +00:00
|
|
|
// we go on to do all the standard track hit tests.
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if( tip==NULL )
|
|
|
|
{
|
|
|
|
ToolsToolBar * ttb = mListener->TP_GetToolsToolBar();
|
|
|
|
if( ttb == NULL )
|
|
|
|
return;
|
2014-06-03 20:30:19 +00:00
|
|
|
// JKC: DetermineToolToUse is called whenever the mouse
|
|
|
|
// moves. I had some worries about calling it when in
|
2010-01-23 19:44:49 +00:00
|
|
|
// multimode as it then has to hit-test all 'objects' in
|
2014-06-03 20:30:19 +00:00
|
|
|
// the track panel, but performance seems fine in
|
2010-01-23 19:44:49 +00:00
|
|
|
// practice (on a P500).
|
|
|
|
int tool = DetermineToolToUse( ttb, event );
|
|
|
|
|
|
|
|
tip = ttb->GetMessageForTool( tool );
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// We don't include the select tool in
|
2010-01-23 19:44:49 +00:00
|
|
|
// SetCursorAndTipByTool() because it's more complex than
|
|
|
|
// the other tool cases.
|
|
|
|
if( tool != selectTool )
|
|
|
|
{
|
|
|
|
SetCursorAndTipByTool( tool, event, &tip);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
bool bMultiToolMode = ttb->IsDown(multiTool);
|
2014-11-08 14:30:19 +00:00
|
|
|
const wxCursor *pSelection = 0;
|
|
|
|
SetCursorAndTipWhenSelectTool
|
|
|
|
( t, event, r, bMultiToolMode, &tip, &pSelection );
|
|
|
|
if (pSelection)
|
|
|
|
// Set cursor once only here, to avoid flashing during drags
|
|
|
|
SetCursor(*pSelection);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tip)
|
|
|
|
mListener->TP_DisplayStatusMessage(tip);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// This method handles various ways of starting and extending
|
2010-01-23 19:44:49 +00:00
|
|
|
/// selections. These are the selections you make by clicking and
|
|
|
|
/// dragging over a waveform.
|
|
|
|
void TrackPanel::HandleSelect(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
wxRect r;
|
|
|
|
Track *t = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
|
|
|
|
// AS: Ok, did the user just click the mouse, release the mouse,
|
|
|
|
// or drag?
|
|
|
|
if (event.LeftDown()) {
|
|
|
|
// AS: Now, did they click in a track somewhere? If so, we want
|
2014-06-03 20:30:19 +00:00
|
|
|
// to extend the current selection or start a new selection,
|
2010-01-23 19:44:49 +00:00
|
|
|
// depending on the shift key. If not, cancel all selections.
|
|
|
|
if (t)
|
|
|
|
SelectionHandleClick(event, t, r);
|
|
|
|
else {
|
|
|
|
SelectNone();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
} else if (event.LeftUp() || event.RightUp()) {
|
|
|
|
if (mSnapManager) {
|
|
|
|
delete mSnapManager;
|
|
|
|
mSnapManager = NULL;
|
|
|
|
}
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
//Send the new selection state to the undo/redo stack:
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
// This stops center snapping with mouse movement
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
} else if (event.LeftDClick() && !event.ShiftDown()) {
|
|
|
|
if (!mCapturedTrack) {
|
|
|
|
wxRect r;
|
|
|
|
mCapturedTrack =
|
|
|
|
FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
if (!mCapturedTrack)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deselect all other tracks and select this one.
|
|
|
|
SelectNone();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mTracks->Select(mCapturedTrack);
|
|
|
|
|
|
|
|
// Default behavior: select whole track
|
|
|
|
SelectTrackLength(mCapturedTrack);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Special case: if we're over a clip in a WaveTrack,
|
|
|
|
// select just that clip
|
|
|
|
if (mCapturedTrack->GetKind() == Track::Wave) {
|
|
|
|
WaveTrack *w = (WaveTrack *)mCapturedTrack;
|
|
|
|
WaveClip *selectedClip = w->GetClipAtX(event.m_x);
|
|
|
|
if (selectedClip) {
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(
|
|
|
|
selectedClip->GetOffset(), selectedClip->GetEndTime());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
//Also, capture this track for dragging until we up-click.
|
|
|
|
mCapturedClipArray.Add(TrackClip(w, selectedClip));
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mMouseCapture = IsSliding;
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
StartSlide(event);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
SetCapturedTrack( NULL );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-11-08 14:30:19 +00:00
|
|
|
#ifdef SPECTRAL_EDITING_ESC_KEY
|
2014-10-25 22:05:45 +00:00
|
|
|
else if (!event.IsButton() &&
|
|
|
|
mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
|
|
|
|
!mViewInfo->selectedRegion.isPoint())
|
|
|
|
MoveSnappingFreqSelection(event.m_y, r.y, r.height, t);
|
2014-11-08 14:30:19 +00:00
|
|
|
#endif
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
done:
|
|
|
|
SelectionHandleDrag(event, t);
|
2010-07-21 04:53:38 +00:00
|
|
|
|
|
|
|
// Update lyrics display for new selection.
|
|
|
|
AudacityProject* pProj = GetActiveProject();
|
|
|
|
LyricsWindow* pLyricsWindow = pProj->GetLyricsWindow();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pLyricsWindow && pLyricsWindow->IsShown())
|
2010-07-21 04:53:38 +00:00
|
|
|
{
|
|
|
|
Lyrics* pLyricsPanel = pLyricsWindow->GetLyricsPanel();
|
|
|
|
pLyricsPanel->Update(pProj->GetSel0());
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 16:01:50 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event)
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetActiveProject();
|
|
|
|
if (p) {
|
|
|
|
double clicktime = PositionToTime(event.m_x, GetLeftOffset());
|
|
|
|
const double t1 = mViewInfo->selectedRegion.t1();
|
|
|
|
// Play to end of selection, or if that is not right of the pick, end of track
|
|
|
|
double endtime = clicktime < t1 ? t1 : mViewInfo->total;
|
|
|
|
|
|
|
|
//Behavior should differ depending upon whether we are
|
|
|
|
//currently in playback mode or not.
|
|
|
|
|
|
|
|
bool busy = gAudioIO->IsBusy();
|
|
|
|
if (!busy)
|
|
|
|
{
|
|
|
|
//If we aren't currently playing back, start playing back at
|
|
|
|
//the clicked point
|
|
|
|
ControlToolBar * ctb = p->GetControlToolBar();
|
|
|
|
//ctb->SetPlay(true);// Not needed as done in PlayPlayRegion
|
2015-04-14 18:52:22 +00:00
|
|
|
ctb->PlayPlayRegion
|
|
|
|
(SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions());
|
2014-11-29 22:09:57 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//If we are playing back, stop and move playback
|
|
|
|
//to the clicked point.
|
|
|
|
//This unpauses paused audio as well. The right thing to do might be to
|
|
|
|
//leave it paused but move the point. This would probably
|
|
|
|
//require a new method in ControlToolBar: SetPause();
|
|
|
|
ControlToolBar * ctb = p->GetControlToolBar();
|
|
|
|
ctb->StopPlaying();
|
2015-04-14 18:52:22 +00:00
|
|
|
ctb->PlayPlayRegion(SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions());
|
2014-11-29 22:09:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// This method gets called when we're handling selection
|
2010-01-23 19:44:49 +00:00
|
|
|
/// and the mouse was just clicked.
|
|
|
|
void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
|
|
|
|
Track * pTrack, wxRect r)
|
|
|
|
{
|
2011-03-13 22:43:48 +00:00
|
|
|
Track *rightTrack = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedTrack = pTrack;
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
mMouseClickX = event.m_x;
|
|
|
|
mMouseClickY = event.m_y;
|
|
|
|
mMouseCapture=IsSelecting;
|
2015-04-12 16:39:11 +00:00
|
|
|
mInitialSelection = mViewInfo->selectedRegion;
|
|
|
|
|
|
|
|
// Save initial state of track selections, also,
|
|
|
|
// if the shift button is down and no track is selected yet,
|
|
|
|
// at least select the track we clicked into.
|
|
|
|
bool isAtLeastOneTrackSelected = false;
|
|
|
|
mInitialTrackSelection->clear();
|
|
|
|
{
|
|
|
|
bool nextTrackIsLinkFromPTrack = false;
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
|
|
|
const bool isSelected = t->GetSelected();
|
|
|
|
mInitialTrackSelection->push_back(isSelected);
|
|
|
|
if (isSelected) {
|
|
|
|
isAtLeastOneTrackSelected = true;
|
|
|
|
}
|
|
|
|
if (!isAtLeastOneTrackSelected) {
|
|
|
|
if (t == pTrack && t->GetLinked()) {
|
|
|
|
nextTrackIsLinkFromPTrack = true;
|
|
|
|
}
|
|
|
|
else if (nextTrackIsLinkFromPTrack) {
|
|
|
|
rightTrack = t;
|
|
|
|
nextTrackIsLinkFromPTrack = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-02-17 06:30:14 +00:00
|
|
|
// We create a new snap manager in case any snap-points have changed
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mSnapManager)
|
|
|
|
delete mSnapManager;
|
2010-02-12 21:28:34 +00:00
|
|
|
|
2010-02-17 06:30:14 +00:00
|
|
|
mSnapManager = new SnapManager(mTracks, NULL,
|
|
|
|
mViewInfo->zoom,
|
|
|
|
4); // pixel tolerance
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
mStretching = false;
|
|
|
|
bool stretch = HitTestStretch(pTrack, r, event);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (event.ShiftDown()
|
2014-11-29 22:09:57 +00:00
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
2014-11-29 22:09:57 +00:00
|
|
|
&& !stretch
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2014-11-29 22:09:57 +00:00
|
|
|
) {
|
2011-03-13 22:43:48 +00:00
|
|
|
if (!isAtLeastOneTrackSelected) {
|
2010-01-23 19:44:49 +00:00
|
|
|
pTrack->SetSelected(true);
|
2011-03-13 22:43:48 +00:00
|
|
|
if (rightTrack)
|
|
|
|
rightTrack->SetSelected(true);
|
|
|
|
else if (pTrack->GetLink())
|
|
|
|
pTrack->GetLink()->SetSelected(true);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
double value;
|
2014-11-11 15:24:22 +00:00
|
|
|
// Shift-click, choose closest boundary
|
2014-10-25 22:05:45 +00:00
|
|
|
SelectionBoundary boundary =
|
|
|
|
ChooseBoundary(event, pTrack, r, false, false, &value);
|
|
|
|
switch (boundary) {
|
|
|
|
case SBLeft:
|
|
|
|
case SBRight:
|
|
|
|
{
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-25 22:05:45 +00:00
|
|
|
// If drag starts, change time selection only
|
|
|
|
// (also exit frequency snapping)
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
#endif
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = true;
|
2014-10-25 22:05:45 +00:00
|
|
|
mSelStart = value;
|
|
|
|
ExtendSelection(event.m_x, r.x, pTrack);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
case SBBottom:
|
|
|
|
case SBTop:
|
|
|
|
{
|
|
|
|
mFreqSelTrack = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
mFreqSelPin = value;
|
|
|
|
mFreqSelMode =
|
|
|
|
(boundary == SBBottom)
|
|
|
|
? FREQ_SEL_BOTTOM_FREE : FREQ_SEL_TOP_FREE;
|
|
|
|
|
|
|
|
// Drag frequency only, not time:
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = false;
|
2014-10-25 22:05:45 +00:00
|
|
|
ExtendFreqSelection(event.m_y, r.y, r.height);
|
|
|
|
break;
|
|
|
|
}
|
2014-11-08 14:30:19 +00:00
|
|
|
case SBCenter:
|
|
|
|
HandleCenterFrequencyClick(true, pTrack, value);
|
|
|
|
break;
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
2014-10-25 22:05:45 +00:00
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
};
|
2014-10-18 14:19:38 +00:00
|
|
|
|
|
|
|
UpdateSelectionDisplay();
|
2014-10-25 22:05:45 +00:00
|
|
|
// For persistence of the selection change:
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// A control-click will set just the indicator to the clicked spot,
|
2014-06-03 20:30:19 +00:00
|
|
|
// and turn playback on.
|
2010-09-18 21:02:36 +00:00
|
|
|
else if(event.CmdDown()
|
|
|
|
#ifdef USE_MIDI
|
2014-11-29 22:09:57 +00:00
|
|
|
&& !stretch
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
StartOrJumpPlayback(event);
|
2015-04-12 16:39:11 +00:00
|
|
|
|
|
|
|
// Not starting a drag
|
|
|
|
SetCapturedTrack(NULL, IsUncaptured);
|
2014-10-18 14:19:38 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-04-12 16:39:11 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Make sure you are within the selected track
|
2015-04-12 16:39:11 +00:00
|
|
|
bool startNewSelection = true;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (pTrack && pTrack->GetSelected()) {
|
|
|
|
// Adjusting selection edges can be turned off in the
|
|
|
|
// preferences now
|
2014-10-18 14:19:38 +00:00
|
|
|
if (mAdjustSelectionEdges) {
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-25 22:05:45 +00:00
|
|
|
bool logF;
|
|
|
|
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
|
|
|
|
isSpectrogramTrack(pTrack, &logF)) {
|
|
|
|
// Ignore whether we are inside the time selection.
|
|
|
|
// Exit center-snapping, start dragging the width.
|
|
|
|
mFreqSelMode = FREQ_SEL_PINNED_CENTER;
|
|
|
|
mFreqSelTrack = static_cast<WaveTrack*>(pTrack);
|
|
|
|
mFreqSelPin = mViewInfo->selectedRegion.fc();
|
|
|
|
// Do not adjust time boundaries
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = false;
|
2014-10-25 22:05:45 +00:00
|
|
|
ExtendFreqSelection(event.m_y, r.y, r.height);
|
|
|
|
UpdateSelectionDisplay();
|
|
|
|
// Frequency selection doesn't persist (yet?), so skip this:
|
|
|
|
// MakeParentModifyState(false);
|
2014-10-18 14:19:38 +00:00
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
2014-10-25 22:05:45 +00:00
|
|
|
{
|
2014-11-11 15:24:22 +00:00
|
|
|
// Not shift-down, choose boundary only within snapping
|
2014-10-25 22:05:45 +00:00
|
|
|
double value;
|
|
|
|
SelectionBoundary boundary =
|
|
|
|
ChooseBoundary(event, pTrack, r, true, true, &value);
|
|
|
|
switch (boundary) {
|
|
|
|
case SBNone:
|
|
|
|
// startNewSelection remains true
|
|
|
|
break;
|
|
|
|
case SBLeft:
|
|
|
|
case SBRight:
|
2014-10-18 14:19:38 +00:00
|
|
|
startNewSelection = false;
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-25 22:05:45 +00:00
|
|
|
// Disable frequency selection
|
2014-10-18 14:19:38 +00:00
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
#endif
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = true;
|
2014-10-25 22:05:45 +00:00
|
|
|
mSelStart = value;
|
|
|
|
break;
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
case SBBottom:
|
|
|
|
case SBTop:
|
2014-11-11 15:24:22 +00:00
|
|
|
case SBWidth:
|
2014-10-18 14:19:38 +00:00
|
|
|
startNewSelection = false;
|
2014-10-25 22:05:45 +00:00
|
|
|
// Disable time selection
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = false;
|
2014-11-11 15:24:22 +00:00
|
|
|
mFreqSelTrack = static_cast<const WaveTrack*>(pTrack);
|
2014-10-25 22:05:45 +00:00
|
|
|
mFreqSelPin = value;
|
2014-11-11 15:24:22 +00:00
|
|
|
mFreqSelMode =
|
|
|
|
(boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
|
|
|
|
(boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
|
|
|
|
FREQ_SEL_TOP_FREE;
|
2014-11-08 14:30:19 +00:00
|
|
|
break;
|
|
|
|
case SBCenter:
|
|
|
|
HandleCenterFrequencyClick(false, pTrack, value);
|
|
|
|
startNewSelection = false;
|
2014-10-25 22:05:45 +00:00
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
} // mAdjustSelectionEdges
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//Determine if user clicked on a label track.
|
|
|
|
if (pTrack && (pTrack->GetKind() == Track::Label))
|
2011-11-24 23:12:52 +00:00
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
LabelTrack *lt = (LabelTrack *) pTrack;
|
|
|
|
if (lt->HandleMouse(event, r,//mCapturedRect,
|
|
|
|
mViewInfo->h, mViewInfo->zoom,
|
2014-10-05 17:10:09 +00:00
|
|
|
&mViewInfo->selectedRegion)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IF the user clicked a label, THEN select all other tracks by Label
|
|
|
|
if (lt->IsSelected()) {
|
2015-04-12 16:39:11 +00:00
|
|
|
mTracks->Select(lt);
|
2010-01-23 19:44:49 +00:00
|
|
|
SelectTracksByLabel( lt );
|
|
|
|
DisplaySelection();
|
2015-04-12 16:39:11 +00:00
|
|
|
|
|
|
|
// Not starting a drag
|
|
|
|
SetCapturedTrack(NULL, IsUncaptured);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (stretch) {
|
|
|
|
NoteTrack *nt = (NoteTrack *) pTrack;
|
|
|
|
// find nearest beat to sel0, sel1
|
|
|
|
double minPeriod = 0.05; // minimum beat period
|
|
|
|
double qBeat0, qBeat1;
|
2013-08-25 21:51:26 +00:00
|
|
|
double centerBeat = 0.0f;
|
2014-10-05 17:10:09 +00:00
|
|
|
mStretchSel0 = nt->NearestBeatTime(mViewInfo->selectedRegion.t0(), &qBeat0);
|
|
|
|
mStretchSel1 = nt->NearestBeatTime(mViewInfo->selectedRegion.t1(), &qBeat1);
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
// If there is not (almost) a beat to stretch that is slower
|
|
|
|
// than 20 beats per second, don't stretch
|
|
|
|
if (within(qBeat0, qBeat1, 0.9) ||
|
|
|
|
(mStretchSel1 - mStretchSel0) / (qBeat1 - qBeat0) < minPeriod) return;
|
|
|
|
|
|
|
|
if (startNewSelection) { // mouse is not at an edge, but after
|
|
|
|
// quantization, we could be indicating the selection edge
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = true;
|
2010-09-18 21:02:36 +00:00
|
|
|
mSelStart = PositionToTime(event.m_x, r.x);
|
|
|
|
mStretchStart = nt->NearestBeatTime(mSelStart, ¢erBeat);
|
|
|
|
if (within(qBeat0, centerBeat, 0.1)) {
|
|
|
|
mListener->TP_DisplayStatusMessage(
|
|
|
|
_("Click and drag to stretch selected region."));
|
|
|
|
SetCursor(*mStretchLeftCursor);
|
|
|
|
// mStretchMode = stretchLeft;
|
2014-10-05 17:10:09 +00:00
|
|
|
mSelStart = mViewInfo->selectedRegion.t1();
|
|
|
|
// condition that implies stretchLeft
|
2010-09-18 21:02:36 +00:00
|
|
|
startNewSelection = false;
|
|
|
|
} else if (within(qBeat1, centerBeat, 0.1)) {
|
|
|
|
mListener->TP_DisplayStatusMessage(
|
|
|
|
_("Click and drag to stretch selected region."));
|
|
|
|
SetCursor(*mStretchRightCursor);
|
|
|
|
// mStretchMode = stretchRight;
|
2014-10-05 17:10:09 +00:00
|
|
|
mSelStart = mViewInfo->selectedRegion.t0();
|
|
|
|
// condition that implies stretchRight
|
2010-09-18 21:02:36 +00:00
|
|
|
startNewSelection = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (startNewSelection) {
|
|
|
|
mStretchMode = stretchCenter;
|
|
|
|
mStretchLeftBeats = qBeat1 - centerBeat;
|
|
|
|
mStretchRightBeats = centerBeat - qBeat0;
|
2014-11-29 17:10:56 +00:00
|
|
|
} else if (mSelStartValid && mViewInfo->selectedRegion.t1() == mSelStart) {
|
2010-09-18 21:02:36 +00:00
|
|
|
// note that at this point, mSelStart is at the opposite
|
|
|
|
// end of the selection from the cursor. If the cursor is
|
|
|
|
// over sel0, then mSelStart is at sel1.
|
|
|
|
mStretchMode = stretchLeft;
|
|
|
|
} else {
|
|
|
|
mStretchMode = stretchRight;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStretchMode == stretchLeft) {
|
|
|
|
mStretchLeftBeats = 0;
|
|
|
|
mStretchRightBeats = qBeat1 - qBeat0;
|
|
|
|
} else if (mStretchMode == stretchRight) {
|
|
|
|
mStretchLeftBeats = qBeat1 - qBeat0;
|
|
|
|
mStretchRightBeats = 0;
|
2013-10-29 21:49:45 +00:00
|
|
|
}
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(mStretchSel0, mStretchSel1);
|
2010-09-18 21:02:36 +00:00
|
|
|
mStretching = true;
|
|
|
|
mStretched = false;
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/* i18n-hint: (noun) The track that is used for MIDI notes which can be
|
2012-04-28 18:17:38 +00:00
|
|
|
dragged to change their duration.*/
|
2014-06-03 20:30:19 +00:00
|
|
|
MakeParentPushState(_("Stretch Note Track"),
|
|
|
|
/* i18n-hint: In the history list, indicates a MIDI note has
|
|
|
|
been dragged to change its duration (stretch it). Using either past
|
|
|
|
or present tense is fine here. If unsure, go for whichever is
|
2012-04-28 18:17:38 +00:00
|
|
|
shorter.*/
|
|
|
|
_("Stretch"));
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
// Full refresh since the label area may need to indicate
|
|
|
|
// newly selected tracks. (I'm really not sure if the label area
|
|
|
|
// needs to be refreshed or how to just refresh non-label areas.-RBD)
|
|
|
|
Refresh(false);
|
|
|
|
|
|
|
|
// Make sure the ruler follows suit.
|
|
|
|
mRuler->DrawSelection();
|
|
|
|
|
|
|
|
// As well as the SelectionBar.
|
|
|
|
DisplaySelection();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
if (startNewSelection) {
|
|
|
|
// If we didn't move a selection boundary, start a new selection
|
|
|
|
SelectNone();
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
StartFreqSelection (event.m_y, r.y, r.height, pTrack);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
StartSelection(event.m_x, r.x);
|
|
|
|
mTracks->Select(pTrack);
|
|
|
|
SetFocusedTrack(pTrack);
|
|
|
|
//On-Demand: check to see if there is an OD thing associated with this track.
|
|
|
|
if (pTrack->GetKind() == Track::Wave) {
|
|
|
|
if(ODManager::IsInstanceCreated())
|
|
|
|
ODManager::Instance()->DemandTrackUpdate((WaveTrack*)pTrack,mSelStart);
|
|
|
|
}
|
|
|
|
DisplaySelection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Reset our selection markers.
|
|
|
|
void TrackPanel::StartSelection(int mouseXCoordinate, int trackLeftEdge)
|
|
|
|
{
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = true;
|
2010-01-23 19:44:49 +00:00
|
|
|
mSelStart = mViewInfo->h + ((mouseXCoordinate - trackLeftEdge)
|
|
|
|
/ mViewInfo->zoom);
|
|
|
|
|
|
|
|
double s = mSelStart;
|
|
|
|
|
|
|
|
if (mSnapManager) {
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
2010-02-02 19:43:52 +00:00
|
|
|
bool snappedPoint, snappedTime;
|
|
|
|
if (mSnapManager->Snap(mCapturedTrack, mSelStart, false,
|
|
|
|
&s, &snappedPoint, &snappedTime)) {
|
|
|
|
if (snappedPoint)
|
|
|
|
mSnapLeft = TimeToPosition(s, trackLeftEdge);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(s, s);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
SonifyBeginModifyState();
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-09-18 21:02:36 +00:00
|
|
|
SonifyEndModifyState();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Extend the existing selection
|
|
|
|
void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge,
|
|
|
|
Track *pTrack)
|
|
|
|
{
|
2014-11-29 17:10:56 +00:00
|
|
|
if (!mSelStartValid)
|
2014-10-18 14:19:38 +00:00
|
|
|
// Must be dragging frequency bounds only.
|
|
|
|
return;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
double selend = PositionToTime(mouseXCoordinate, trackLeftEdge);
|
|
|
|
clip_bottom(selend, 0.0);
|
|
|
|
|
|
|
|
double origSel0, origSel1;
|
|
|
|
double sel0, sel1;
|
|
|
|
|
|
|
|
if (pTrack == NULL && mCapturedTrack != NULL)
|
|
|
|
pTrack = mCapturedTrack;
|
|
|
|
|
|
|
|
if (mSelStart < selend) {
|
|
|
|
sel0 = mSelStart;
|
|
|
|
sel1 = selend;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sel1 = mSelStart;
|
|
|
|
sel0 = selend;
|
|
|
|
}
|
|
|
|
|
|
|
|
origSel0 = sel0;
|
|
|
|
origSel1 = sel1;
|
|
|
|
|
|
|
|
if (mSnapManager) {
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
2010-02-02 19:43:52 +00:00
|
|
|
bool snappedPoint, snappedTime;
|
|
|
|
if (mSnapManager->Snap(mCapturedTrack, sel0, false,
|
|
|
|
&sel0, &snappedPoint, &snappedTime)) {
|
|
|
|
if (snappedPoint)
|
|
|
|
mSnapLeft = TimeToPosition(sel0, trackLeftEdge);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-02-02 19:43:52 +00:00
|
|
|
if (mSnapManager->Snap(mCapturedTrack, sel1, true,
|
|
|
|
&sel1, &snappedPoint, &snappedTime)) {
|
|
|
|
if (snappedPoint)
|
|
|
|
mSnapRight = TimeToPosition(sel1, trackLeftEdge);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-02-02 19:43:52 +00:00
|
|
|
// Check if selection endpoints are too close together to snap (unless
|
|
|
|
// using snap-to-time -- then we always accept the snap results)
|
|
|
|
if (mSnapLeft >= 0 && mSnapRight >= 0 && mSnapRight - mSnapLeft < 3 &&
|
|
|
|
!snappedTime) {
|
2010-01-23 19:44:49 +00:00
|
|
|
sel0 = origSel0;
|
|
|
|
sel1 = origSel1;
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(sel0, sel1);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//On-Demand: check to see if there is an OD thing associated with this track. If so we want to update the focal point for the task.
|
2011-11-24 23:12:52 +00:00
|
|
|
if (pTrack && (pTrack->GetKind() == Track::Wave) && ODManager::IsInstanceCreated())
|
|
|
|
ODManager::Instance()->DemandTrackUpdate((WaveTrack*)pTrack,sel0); //sel0 is sometimes less than mSelStart
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
void TrackPanel::UpdateSelectionDisplay()
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
// Full refresh since the label area may need to indicate
|
|
|
|
// newly selected tracks.
|
|
|
|
Refresh(false);
|
|
|
|
|
|
|
|
// Make sure the ruler follows suit.
|
|
|
|
mRuler->DrawSelection();
|
|
|
|
|
|
|
|
// As well as the SelectionBar.
|
|
|
|
DisplaySelection();
|
|
|
|
}
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
inline double findMaxRatio(double center, double rate)
|
|
|
|
{
|
|
|
|
const double minFrequency = 1.0;
|
|
|
|
const double maxFrequency = (rate / 2.0);
|
|
|
|
const double frequency =
|
|
|
|
std::min(maxFrequency,
|
|
|
|
std::max(minFrequency, center));
|
|
|
|
return
|
|
|
|
std::min(frequency / minFrequency, maxFrequency / frequency);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-12-22 08:03:41 +00:00
|
|
|
void TrackPanel::SnapCenterOnce(WaveTrack *pTrack, bool up)
|
|
|
|
{
|
|
|
|
const int windowSize = mTrackArtist->GetSpectrumWindowSize();
|
|
|
|
const double rate = pTrack->GetRate();
|
|
|
|
const double nyq = rate / 2.0;
|
|
|
|
const double binFrequency = rate / windowSize;
|
|
|
|
|
|
|
|
double f1 = mViewInfo->selectedRegion.f1();
|
|
|
|
double centerFrequency = mViewInfo->selectedRegion.fc();
|
|
|
|
if (centerFrequency <= 0) {
|
|
|
|
centerFrequency = up ? binFrequency : nyq;
|
|
|
|
f1 = centerFrequency * sqrt(2.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
const double ratio = f1 / centerFrequency;
|
|
|
|
const int originalBin = floor(0.5 + centerFrequency / binFrequency);
|
|
|
|
const int limitingBin = up ? floor(0.5 + nyq / binFrequency) : 1;
|
|
|
|
|
|
|
|
// This is crude and wasteful, doing the FFT each time the command is called.
|
|
|
|
// It would be better to cache the data, but then invalidation of the cache would
|
|
|
|
// need doing in all places that change the time selection.
|
|
|
|
StartSnappingFreqSelection(pTrack);
|
|
|
|
double snappedFrequency = centerFrequency;
|
|
|
|
int bin = originalBin;
|
|
|
|
if (up) {
|
|
|
|
while (snappedFrequency <= centerFrequency &&
|
|
|
|
bin < limitingBin)
|
|
|
|
snappedFrequency = mFrequencySnapper->FindPeak(++bin * binFrequency, NULL);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
while (snappedFrequency >= centerFrequency &&
|
|
|
|
bin > limitingBin)
|
|
|
|
snappedFrequency = mFrequencySnapper->FindPeak(--bin * binFrequency, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mViewInfo->selectedRegion.setFrequencies
|
|
|
|
(snappedFrequency / ratio, snappedFrequency * ratio);
|
|
|
|
}
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
void TrackPanel::StartSnappingFreqSelection (WaveTrack *pTrack)
|
|
|
|
{
|
|
|
|
static const sampleCount minLength = 8;
|
|
|
|
|
|
|
|
const double rate = pTrack->GetRate();
|
|
|
|
|
|
|
|
// Grab samples, just for this track, at these times
|
|
|
|
std::vector<float> frequencySnappingData;
|
|
|
|
const sampleCount start =
|
|
|
|
pTrack->TimeToLongSamples(mViewInfo->selectedRegion.t0());
|
|
|
|
const sampleCount end =
|
|
|
|
pTrack->TimeToLongSamples(mViewInfo->selectedRegion.t1());
|
|
|
|
const sampleCount length =
|
|
|
|
std::min(sampleCount(frequencySnappingData.max_size()),
|
|
|
|
std::min(sampleCount(10485760), // as in FreqWindow.cpp
|
|
|
|
end - start));
|
|
|
|
const sampleCount effectiveLength = std::max(minLength, length);
|
|
|
|
frequencySnappingData.resize(effectiveLength, 0.0f);
|
|
|
|
pTrack->Get(
|
|
|
|
reinterpret_cast<samplePtr>(&frequencySnappingData[0]),
|
|
|
|
floatSample, start, length);
|
|
|
|
|
|
|
|
// Use same settings as are now used for spectrogram display,
|
|
|
|
// except, shrink the window as needed so we get some answers
|
|
|
|
int windowSize = mTrackArtist->GetSpectrumWindowSize();
|
|
|
|
while(windowSize > effectiveLength)
|
|
|
|
windowSize >>= 1;
|
|
|
|
int windowType;
|
|
|
|
gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, 3);
|
|
|
|
mFrequencySnapper->Calculate(
|
|
|
|
SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
|
|
|
|
&frequencySnappingData[0], length);
|
|
|
|
|
|
|
|
// We can now throw away the sample data but we keep the spectrum.
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::MoveSnappingFreqSelection (int mouseYCoordinate,
|
|
|
|
int trackTopEdge,
|
|
|
|
int trackHeight, Track *pTrack)
|
|
|
|
{
|
2014-10-25 22:05:45 +00:00
|
|
|
bool logF;
|
2014-10-18 14:19:38 +00:00
|
|
|
if (pTrack &&
|
|
|
|
pTrack->GetSelected() &&
|
2014-10-25 22:05:45 +00:00
|
|
|
isSpectrogramTrack(pTrack, &logF)) {
|
|
|
|
WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
|
|
|
|
// PRL:
|
2014-11-08 14:30:19 +00:00
|
|
|
// What happens if center snapping selection began in one spectrogram track,
|
2014-10-25 22:05:45 +00:00
|
|
|
// then continues inside another? We do not then recalculate
|
|
|
|
// the spectrum (as was done in StartSnappingFreqSelection)
|
|
|
|
// but snap according to the peaks in the old track.
|
|
|
|
// I am not worrying about that odd case.
|
|
|
|
const double rate = wt->GetRate();
|
|
|
|
const double frequency =
|
|
|
|
PositionToFrequency(false, mouseYCoordinate,
|
|
|
|
trackTopEdge, trackHeight, rate, logF);
|
|
|
|
const double snappedFrequency =
|
|
|
|
mFrequencySnapper->FindPeak(frequency, NULL);
|
|
|
|
const double maxRatio = findMaxRatio(snappedFrequency, rate);
|
|
|
|
double ratio = 2.0; // An arbitrary octave on each side, at most
|
2014-10-18 14:19:38 +00:00
|
|
|
{
|
2014-10-25 22:05:45 +00:00
|
|
|
const double f0 = mViewInfo->selectedRegion.f0();
|
|
|
|
const double f1 = mViewInfo->selectedRegion.f1();
|
|
|
|
if (f1 >= f0 && f0 >= 0)
|
2014-10-18 14:19:38 +00:00
|
|
|
// Preserve already chosen ratio instead
|
2014-10-25 22:05:45 +00:00
|
|
|
ratio = sqrt(f1 / f0);
|
|
|
|
}
|
|
|
|
ratio = std::min(ratio, maxRatio);
|
2014-10-18 14:19:38 +00:00
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
mFreqSelPin = snappedFrequency;
|
|
|
|
mViewInfo->selectedRegion.setFrequencies(
|
|
|
|
snappedFrequency / ratio, snappedFrequency * ratio);
|
2014-10-18 14:19:38 +00:00
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
mFreqSelTrack = wt;
|
|
|
|
// SelectNone();
|
|
|
|
// mTracks->Select(pTrack);
|
|
|
|
SetFocusedTrack(pTrack);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::StartFreqSelection (int mouseYCoordinate, int trackTopEdge,
|
|
|
|
int trackHeight, Track *pTrack)
|
|
|
|
{
|
|
|
|
mFreqSelTrack = 0;
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
2014-10-25 22:05:45 +00:00
|
|
|
mFreqSelPin = SelectedRegion::UndefinedFrequency;
|
|
|
|
|
|
|
|
bool logF;
|
|
|
|
if (isSpectrogramTrack(pTrack, &logF)) {
|
|
|
|
mFreqSelTrack = static_cast<WaveTrack*>(pTrack);
|
|
|
|
mFreqSelMode = FREQ_SEL_FREE;
|
|
|
|
const double rate = mFreqSelTrack->GetRate();
|
|
|
|
mFreqSelPin =
|
|
|
|
PositionToFrequency(false, mouseYCoordinate,
|
|
|
|
trackTopEdge, trackHeight, rate, logF);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
mViewInfo->selectedRegion.setFrequencies(mFreqSelPin, mFreqSelPin);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::ExtendFreqSelection(int mouseYCoordinate, int trackTopEdge,
|
2014-10-25 22:05:45 +00:00
|
|
|
int trackHeight)
|
2014-10-18 14:19:38 +00:00
|
|
|
{
|
|
|
|
// When dragWidth is true, and not dragging the center,
|
|
|
|
// adjust both top and bottom about geometric mean.
|
|
|
|
|
|
|
|
if (mFreqSelMode == FREQ_SEL_INVALID ||
|
|
|
|
mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Extension happens only when dragging in the same track in which we
|
|
|
|
// started, and that is of a spectrogram display type.
|
|
|
|
|
|
|
|
const WaveTrack* wt = mFreqSelTrack;
|
|
|
|
const int display = wt->GetDisplay();
|
|
|
|
const bool logF = display == WaveTrack::SpectrumLogDisplay;
|
|
|
|
const double rate = wt->GetRate();
|
|
|
|
const double frequency =
|
|
|
|
PositionToFrequency(true, mouseYCoordinate,
|
|
|
|
trackTopEdge, trackHeight, rate, logF);
|
2014-10-18 16:01:50 +00:00
|
|
|
|
|
|
|
// Dragging center?
|
2014-10-18 14:19:38 +00:00
|
|
|
if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
|
|
|
|
if (frequency == rate || frequency < 1.0)
|
|
|
|
// snapped to top or bottom
|
|
|
|
mViewInfo->selectedRegion.setFrequencies(
|
|
|
|
SelectedRegion::UndefinedFrequency,
|
|
|
|
SelectedRegion::UndefinedFrequency);
|
|
|
|
else {
|
2014-10-25 22:05:45 +00:00
|
|
|
// mFreqSelPin holds the ratio of top to center
|
2014-10-18 14:19:38 +00:00
|
|
|
const double maxRatio = findMaxRatio(frequency, rate);
|
2014-10-25 22:05:45 +00:00
|
|
|
const double ratio = std::min(maxRatio, mFreqSelPin);
|
2014-10-18 14:19:38 +00:00
|
|
|
mViewInfo->selectedRegion.setFrequencies(
|
|
|
|
frequency / ratio, frequency * ratio);
|
|
|
|
}
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
else if (mFreqSelMode == FREQ_SEL_PINNED_CENTER) {
|
|
|
|
if (mFreqSelPin >= 0) {
|
|
|
|
// Change both upper and lower edges leaving centre where it is.
|
|
|
|
if (frequency == rate || frequency < 1.0)
|
|
|
|
// snapped to top or bottom
|
|
|
|
mViewInfo->selectedRegion.setFrequencies(
|
|
|
|
SelectedRegion::UndefinedFrequency,
|
|
|
|
SelectedRegion::UndefinedFrequency);
|
|
|
|
else {
|
|
|
|
// Given center and mouse position, find ratio of the larger to the
|
|
|
|
// smaller, limit that to the frequency scale bounds, and adjust
|
|
|
|
// top and bottom accordingly.
|
|
|
|
const double maxRatio = findMaxRatio(mFreqSelPin, rate);
|
|
|
|
double ratio = frequency / mFreqSelPin;
|
|
|
|
if (ratio < 1.0)
|
|
|
|
ratio = 1.0 / ratio;
|
|
|
|
ratio = std::min(maxRatio, ratio);
|
|
|
|
mViewInfo->selectedRegion.setFrequencies(
|
|
|
|
mFreqSelPin / ratio, mFreqSelPin * ratio);
|
|
|
|
}
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2014-10-25 22:05:45 +00:00
|
|
|
// Dragging of upper or lower.
|
2014-10-18 14:19:38 +00:00
|
|
|
const bool bottomDefined =
|
2014-10-25 22:05:45 +00:00
|
|
|
!(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelPin < 0);
|
2014-10-18 14:19:38 +00:00
|
|
|
const bool topDefined =
|
2014-10-25 22:05:45 +00:00
|
|
|
!(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelPin < 0);
|
|
|
|
if (!bottomDefined || (topDefined && mFreqSelPin < frequency)) {
|
2014-10-18 14:19:38 +00:00
|
|
|
// Adjust top
|
|
|
|
if (frequency == rate)
|
|
|
|
// snapped high; upper frequency is undefined
|
|
|
|
mViewInfo->selectedRegion.setF1(SelectedRegion::UndefinedFrequency);
|
|
|
|
else
|
|
|
|
mViewInfo->selectedRegion.setF1(std::max(1.0, frequency));
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
mViewInfo->selectedRegion.setF0(mFreqSelPin);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Adjust bottom
|
|
|
|
if (frequency < 1.0)
|
|
|
|
// snapped low; lower frequency is undefined
|
|
|
|
mViewInfo->selectedRegion.setF0(SelectedRegion::UndefinedFrequency);
|
|
|
|
else
|
|
|
|
mViewInfo->selectedRegion.setF0(std::min(rate / 2.0, frequency));
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
mViewInfo->selectedRegion.setF1(mFreqSelPin);
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
|
2014-11-29 16:53:28 +00:00
|
|
|
void TrackPanel::ToggleSpectralSelection()
|
|
|
|
{
|
|
|
|
SelectedRegion ®ion = mViewInfo->selectedRegion;
|
|
|
|
const double f0 = region.f0();
|
|
|
|
const double f1 = region.f1();
|
|
|
|
const bool haveSpectralSelection =
|
|
|
|
!(f0 == SelectedRegion::UndefinedFrequency &&
|
|
|
|
f1 == SelectedRegion::UndefinedFrequency);
|
|
|
|
if (haveSpectralSelection)
|
|
|
|
{
|
|
|
|
mLastF0 = f0;
|
|
|
|
mLastF1 = f1;
|
|
|
|
region.setFrequencies
|
|
|
|
(SelectedRegion::UndefinedFrequency, SelectedRegion::UndefinedFrequency);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
region.setFrequencies(mLastF0, mLastF1);
|
|
|
|
}
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
void TrackPanel::ResetFreqSelectionPin(double hintFrequency, bool logF)
|
|
|
|
{
|
|
|
|
switch (mFreqSelMode) {
|
|
|
|
case FREQ_SEL_INVALID:
|
|
|
|
case FREQ_SEL_SNAPPING_CENTER:
|
|
|
|
mFreqSelPin = -1.0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_PINNED_CENTER:
|
|
|
|
mFreqSelPin = mViewInfo->selectedRegion.fc();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_DRAG_CENTER:
|
|
|
|
{
|
|
|
|
// Re-pin the width
|
|
|
|
const double f0 = mViewInfo->selectedRegion.f0();
|
|
|
|
const double f1 = mViewInfo->selectedRegion.f1();
|
|
|
|
if (f0 >= 0 && f1 >= 0)
|
|
|
|
mFreqSelPin = sqrt(f1 / f0);
|
|
|
|
else
|
|
|
|
mFreqSelPin = -1.0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_FREE:
|
|
|
|
// Pin which? Farther from the hint which is the presumed
|
|
|
|
// mouse position.
|
|
|
|
{
|
|
|
|
const double f0 = mViewInfo->selectedRegion.f0();
|
|
|
|
const double f1 = mViewInfo->selectedRegion.f1();
|
|
|
|
if (logF) {
|
|
|
|
if (f1 < 0)
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else {
|
|
|
|
const double logf1 = log(std::max(1.0, f1));
|
|
|
|
const double logf0 = log(std::max(1.0, f0));
|
|
|
|
const double logHint = log(std::max(1.0, hintFrequency));
|
|
|
|
if (abs (logHint - logf1) < abs (logHint - logf0))
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else
|
|
|
|
mFreqSelPin = f1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (f1 < 0 ||
|
|
|
|
abs (hintFrequency - f1) < abs (hintFrequency - f0))
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else
|
|
|
|
mFreqSelPin = f1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_TOP_FREE:
|
|
|
|
mFreqSelPin = mViewInfo->selectedRegion.f0();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_BOTTOM_FREE:
|
|
|
|
mFreqSelPin = mViewInfo->selectedRegion.f1();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
}
|
|
|
|
}
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge,
|
|
|
|
Track *pTrack)
|
|
|
|
{
|
|
|
|
if (mStretched) { // Undo stretch and redo it with new mouse coordinates
|
|
|
|
// Drag handling was not originally implemented with Undo in mind --
|
|
|
|
// there are saved pointers to tracks that are not supposed to change.
|
|
|
|
// Undo will change tracks, so convert pTrack, mCapturedTrack to index
|
|
|
|
// values, then look them up after the Undo
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
int pTrackIndex = pTrack->GetIndex();
|
2014-06-03 20:30:19 +00:00
|
|
|
int capturedTrackIndex =
|
2010-09-18 21:02:36 +00:00
|
|
|
(mCapturedTrack ? mCapturedTrack->GetIndex() : 0);
|
|
|
|
|
|
|
|
GetProject()->OnUndo();
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Undo brings us back to the pre-click state, but we want to
|
2010-09-18 21:02:36 +00:00
|
|
|
// quantize selected region to integer beat boundaries. These
|
|
|
|
// were saved in mStretchSel[12] variables:
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(mStretchSel0, mStretchSel1);
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
mStretched = false;
|
|
|
|
int index = 0;
|
|
|
|
for (Track *t = iter.First(mTracks); t; t = iter.Next()) {
|
|
|
|
if (index == pTrackIndex) pTrack = t;
|
|
|
|
if (mCapturedTrack && index == capturedTrackIndex) mCapturedTrack = t;
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pTrack == NULL && mCapturedTrack != NULL)
|
|
|
|
pTrack = mCapturedTrack;
|
|
|
|
|
|
|
|
if (!pTrack || pTrack->GetKind() != Track::Note) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NoteTrack *pNt = (NoteTrack *) pTrack;
|
|
|
|
double moveto = PositionToTime(mouseXCoordinate, trackLeftEdge);
|
|
|
|
|
|
|
|
// check to make sure tempo is not higher than 20 beats per second
|
|
|
|
// (In principle, tempo can be higher, but not infinity.)
|
|
|
|
double minPeriod = 0.05; // minimum beat period
|
|
|
|
double qBeat0, qBeat1;
|
2014-10-05 17:10:09 +00:00
|
|
|
pNt->NearestBeatTime(mViewInfo->selectedRegion.t0(), &qBeat0); // get beat
|
|
|
|
pNt->NearestBeatTime(mViewInfo->selectedRegion.t1(), &qBeat1);
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
// We could be moving 3 things: left edge, right edge, a point between
|
|
|
|
switch (mStretchMode) {
|
|
|
|
case stretchLeft: {
|
|
|
|
// make sure target duration is not too short
|
2014-10-05 17:10:09 +00:00
|
|
|
double dur = mViewInfo->selectedRegion.t1() - moveto;
|
2010-09-18 21:02:36 +00:00
|
|
|
if (dur < mStretchRightBeats * minPeriod) {
|
|
|
|
dur = mStretchRightBeats * minPeriod;
|
2014-10-05 17:10:09 +00:00
|
|
|
moveto = mViewInfo->selectedRegion.t1() - dur;
|
2013-10-29 21:49:45 +00:00
|
|
|
}
|
2010-09-18 21:02:36 +00:00
|
|
|
if (pNt->StretchRegion(mStretchSel0, mStretchSel1, dur)) {
|
|
|
|
pNt->SetOffset(pNt->GetOffset() + moveto - mStretchSel0);
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(moveto);
|
2010-09-18 21:02:36 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case stretchRight: {
|
|
|
|
// make sure target duration is not too short
|
2014-10-05 17:10:09 +00:00
|
|
|
double dur = moveto - mViewInfo->selectedRegion.t0();
|
2010-09-18 21:02:36 +00:00
|
|
|
if (dur < mStretchLeftBeats * minPeriod) {
|
|
|
|
dur = mStretchLeftBeats * minPeriod;
|
|
|
|
moveto = mStretchSel0 + dur;
|
|
|
|
}
|
|
|
|
if (pNt->StretchRegion(mStretchSel0, mStretchSel1, dur)) {
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT1(moveto);
|
2010-09-18 21:02:36 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case stretchCenter: {
|
|
|
|
// make sure both left and right target durations are not too short
|
2014-10-05 17:10:09 +00:00
|
|
|
double left_dur = moveto - mViewInfo->selectedRegion.t0();
|
|
|
|
double right_dur = mViewInfo->selectedRegion.t1() - moveto;
|
2010-09-18 21:02:36 +00:00
|
|
|
double centerBeat;
|
|
|
|
pNt->NearestBeatTime(mSelStart, ¢erBeat);
|
|
|
|
if (left_dur < mStretchLeftBeats * minPeriod) {
|
|
|
|
left_dur = mStretchLeftBeats * minPeriod;
|
|
|
|
moveto = mStretchSel0 + left_dur;
|
|
|
|
}
|
|
|
|
if (right_dur < mStretchRightBeats * minPeriod) {
|
|
|
|
right_dur = mStretchRightBeats * minPeriod;
|
|
|
|
moveto = mStretchSel1 - right_dur;
|
|
|
|
}
|
|
|
|
pNt->StretchRegion(mStretchStart, mStretchSel1, right_dur);
|
|
|
|
pNt->StretchRegion(mStretchSel0, mStretchStart, left_dur);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
break;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
MakeParentPushState(_("Stretch Note Track"), _("Stretch"),
|
2015-04-07 11:20:50 +00:00
|
|
|
PUSH_CONSOLIDATE | PUSH_AUTOSAVE);
|
2010-09-18 21:02:36 +00:00
|
|
|
mStretched = true;
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// AS: If we're dragging to extend a selection (or actually,
|
|
|
|
/// if the screen is scrolling while you're selecting), we
|
|
|
|
/// handle it here.
|
|
|
|
void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
|
|
|
|
{
|
|
|
|
// AS: If we're not in the process of selecting (set in
|
|
|
|
// the SelectionHandleClick above), fuhggeddaboudit.
|
|
|
|
if (mMouseCapture!=IsSelecting)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Also fuhggeddaboudit if we're not dragging and not autoscrolling.
|
2014-11-29 22:09:57 +00:00
|
|
|
if (!event.Dragging() && !mAutoScrolling)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
if (event.CmdDown()) {
|
|
|
|
// Ctrl-drag has no meaning, fuhggeddaboudit
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect r = mCapturedRect;
|
|
|
|
Track *pTrack = mCapturedTrack;
|
|
|
|
|
|
|
|
// AS: Note that FindTrack will replace r's value.
|
|
|
|
if (!pTrack)
|
|
|
|
pTrack = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
|
|
|
|
// Also fuhggeddaboudit if not in a track.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!pTrack)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
int x = mAutoScrolling ? mMouseMostRecentX : event.m_x;
|
|
|
|
int y = mAutoScrolling ? mMouseMostRecentY : event.m_y;
|
|
|
|
|
|
|
|
// JKC: Logic to prevent a selection smaller than 5 pixels to
|
|
|
|
// prevent accidental dragging when selecting.
|
|
|
|
// (if user really wants a tiny selection, they should zoom in).
|
|
|
|
// Can someone make this value of '5' configurable in
|
|
|
|
// preferences?
|
|
|
|
const int minimumSizedSelection = 5; //measured in pixels
|
2014-10-18 14:19:38 +00:00
|
|
|
|
|
|
|
// Might be dragging frequency bounds only, test
|
2014-11-29 17:10:56 +00:00
|
|
|
if (mSelStartValid) {
|
2014-10-18 14:19:38 +00:00
|
|
|
wxInt64 SelStart=TimeToPosition( mSelStart, r.x); //cvt time to pixels.
|
|
|
|
// Abandon this drag if selecting < 5 pixels.
|
|
|
|
if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI // limiting selection size is good, and not starting
|
2014-10-18 14:19:38 +00:00
|
|
|
&& !mStretching // stretch unless mouse moves 5 pixels is good, but
|
2014-06-03 20:30:19 +00:00
|
|
|
#endif // once stretching starts, it's ok to move even 1 pixel
|
2014-10-18 14:19:38 +00:00
|
|
|
)
|
|
|
|
return;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Handle which tracks are selected
|
2014-10-18 14:19:38 +00:00
|
|
|
Track *sTrack = pTrack;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (Track *eTrack = FindTrack(x, y, false, false, NULL)) {
|
|
|
|
// Swap the track pointers if needed
|
2014-10-18 14:19:38 +00:00
|
|
|
if (eTrack->GetIndex() < sTrack->GetIndex()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
Track *t = eTrack;
|
2014-10-18 14:19:38 +00:00
|
|
|
eTrack = sTrack;
|
|
|
|
sTrack = t;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TrackListIterator iter(mTracks);
|
2014-10-18 14:19:38 +00:00
|
|
|
sTrack = iter.StartWith(sTrack);
|
2010-01-23 19:44:49 +00:00
|
|
|
do {
|
2014-10-18 14:19:38 +00:00
|
|
|
mTracks->Select(sTrack);
|
|
|
|
if (sTrack == eTrack) {
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
sTrack = iter.Next();
|
|
|
|
} while (sTrack);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (mStretching) {
|
|
|
|
// the following is also in ExtendSelection, called below
|
|
|
|
// probably a good idea to "hoist" the code to before this "if" stmt
|
|
|
|
if (clickedTrack == NULL && mCapturedTrack != NULL)
|
|
|
|
clickedTrack = mCapturedTrack;
|
|
|
|
Stretch(x, r.x, clickedTrack);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2014-10-18 14:19:38 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-11-08 14:30:19 +00:00
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
|
|
|
|
!mViewInfo->selectedRegion.isPoint())
|
|
|
|
MoveSnappingFreqSelection(y, r.y, r.height, pTrack);
|
|
|
|
else
|
|
|
|
#endif
|
2014-10-18 14:19:38 +00:00
|
|
|
if (mFreqSelTrack == pTrack)
|
2014-10-25 22:05:45 +00:00
|
|
|
ExtendFreqSelection(y, r.y, r.height);
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
ExtendSelection(x, r.x, clickedTrack);
|
2014-10-18 14:19:38 +00:00
|
|
|
UpdateSelectionDisplay();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// Converts a position (mouse X coordinate) to
|
2010-01-23 19:44:49 +00:00
|
|
|
/// project time, in seconds. Needs the left edge of
|
|
|
|
/// the track as an additional parameter.
|
|
|
|
double TrackPanel::PositionToTime(wxInt64 mouseXCoordinate,
|
|
|
|
wxInt64 trackLeftEdge) const
|
|
|
|
{
|
|
|
|
return mViewInfo->h + ((mouseXCoordinate - trackLeftEdge)
|
|
|
|
/ mViewInfo->zoom);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// STM: Converts a project time to screen x position.
|
|
|
|
wxInt64 TrackPanel::TimeToPosition(double projectTime,
|
|
|
|
wxInt64 trackLeftEdge) const
|
|
|
|
{
|
|
|
|
return static_cast <
|
|
|
|
wxInt64 >(mViewInfo->zoom * (projectTime - mViewInfo->h) +
|
|
|
|
trackLeftEdge);
|
|
|
|
}
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-25 22:05:45 +00:00
|
|
|
// Seems 4 is too small to work at the top. Why?
|
|
|
|
enum { FREQ_SNAP_DISTANCE = 10 };
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
/// Converts a position (mouse Y coordinate) to
|
|
|
|
/// frequency, in Hz.
|
|
|
|
double TrackPanel::PositionToFrequency(bool maySnap,
|
|
|
|
wxInt64 mouseYCoordinate,
|
|
|
|
wxInt64 trackTopEdge,
|
|
|
|
int trackHeight,
|
|
|
|
double rate,
|
|
|
|
bool logF) const
|
|
|
|
{
|
|
|
|
// Handle snapping
|
|
|
|
if (maySnap &&
|
|
|
|
mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
|
|
|
|
return rate;
|
|
|
|
if (maySnap &&
|
|
|
|
trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
|
|
|
|
const int freq = lrint(rate/2.);
|
|
|
|
|
|
|
|
if (logF)
|
|
|
|
{
|
|
|
|
const double maxFreq =
|
|
|
|
std::min(freq, mTrackArtist->GetSpectrumLogMaxFreq(freq));
|
|
|
|
const double minFreq =
|
|
|
|
std::max(1, mTrackArtist->GetSpectrumLogMinFreq(1));
|
|
|
|
return exp(p * log(minFreq) + (1.0 - p) * log(maxFreq));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const double maxFreq =
|
|
|
|
std::min(freq, mTrackArtist->GetSpectrumMaxFreq(freq));
|
|
|
|
const double minFreq =
|
|
|
|
std::max(0, mTrackArtist->GetSpectrumMinFreq(0));
|
|
|
|
return p * minFreq + (1.0 - p) * maxFreq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a frequency to screen y position.
|
|
|
|
wxInt64 TrackPanel::FrequencyToPosition(double frequency,
|
|
|
|
wxInt64 trackTopEdge,
|
|
|
|
int trackHeight,
|
|
|
|
double rate,
|
|
|
|
bool logF) const
|
|
|
|
{
|
|
|
|
const int freq = lrint(rate/2.);
|
|
|
|
double p = 0;
|
|
|
|
|
|
|
|
if (logF)
|
|
|
|
{
|
|
|
|
const double maxFreq =
|
|
|
|
std::min(freq, mTrackArtist->GetSpectrumLogMaxFreq(freq));
|
|
|
|
const double minFreq =
|
|
|
|
std::max(1, mTrackArtist->GetSpectrumLogMinFreq(1));
|
|
|
|
if (maxFreq > minFreq)
|
|
|
|
{
|
|
|
|
const double
|
|
|
|
logFrequency = log(frequency < 1.0 ? 1.0 : frequency),
|
|
|
|
logMinFreq = log(minFreq),
|
|
|
|
logMaxFreq = log(maxFreq);
|
|
|
|
p = (logFrequency - logMinFreq) / (logMaxFreq - logMinFreq);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const double maxFreq =
|
|
|
|
std::min(freq, mTrackArtist->GetSpectrumMaxFreq(freq));
|
|
|
|
const double minFreq =
|
|
|
|
std::max(0, mTrackArtist->GetSpectrumMinFreq(0));
|
|
|
|
if (maxFreq > minFreq)
|
|
|
|
p = (frequency - minFreq) / (maxFreq - minFreq);
|
|
|
|
}
|
|
|
|
|
|
|
|
return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
template<typename T>
|
|
|
|
inline void SetIfNotNull( T * pValue, const T Value )
|
2014-10-25 22:05:45 +00:00
|
|
|
{
|
|
|
|
if( pValue == NULL )
|
|
|
|
return;
|
|
|
|
*pValue = Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
TrackPanel::SelectionBoundary TrackPanel::ChooseTimeBoundary
|
|
|
|
(double selend, bool onlyWithinSnapDistance,
|
|
|
|
wxInt64 *pPixelDist, double *pPinValue) const
|
|
|
|
{
|
|
|
|
const double t0 = mViewInfo->selectedRegion.t0();
|
|
|
|
const double t1 = mViewInfo->selectedRegion.t1();
|
|
|
|
wxInt64 pixelDist = mViewInfo->zoom * fabs(selend - t0);
|
|
|
|
bool chooseLeft = true;
|
|
|
|
|
|
|
|
if (mViewInfo->selectedRegion.isPoint())
|
|
|
|
// Special case when selection is a point, and thus left
|
|
|
|
// and right distances are the same
|
|
|
|
chooseLeft = (selend < t0);
|
|
|
|
else {
|
|
|
|
const wxInt64 rightDist = mViewInfo->zoom * fabs(selend - t1);
|
|
|
|
if (rightDist < pixelDist)
|
|
|
|
chooseLeft = false, pixelDist = rightDist;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetIfNotNull(pPixelDist, pixelDist);
|
|
|
|
|
|
|
|
if (onlyWithinSnapDistance &&
|
|
|
|
pixelDist >= SELECTION_RESIZE_REGION) {
|
|
|
|
SetIfNotNull( pPinValue, -1.0);
|
|
|
|
return SBNone;
|
|
|
|
}
|
|
|
|
else if (chooseLeft) {
|
|
|
|
SetIfNotNull( pPinValue, t1);
|
|
|
|
return SBLeft;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SetIfNotNull( pPinValue, t0);
|
|
|
|
return SBRight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
TrackPanel::SelectionBoundary TrackPanel::ChooseBoundary
|
|
|
|
(wxMouseEvent & event, const Track *pTrack, const wxRect &rect,
|
2014-11-08 14:30:19 +00:00
|
|
|
bool mayDragWidth, bool onlyWithinSnapDistance,
|
2014-10-25 22:05:45 +00:00
|
|
|
double *pPinValue) const
|
|
|
|
{
|
|
|
|
// Choose one of four boundaries to adjust, or the center frequency.
|
|
|
|
// May choose frequencies only if in a spectrogram view and
|
|
|
|
// within the time boundaries.
|
|
|
|
// May choose no boundary if onlyWithinSnapDistance is true.
|
|
|
|
// Otherwise choose the eligible boundary nearest the mouse click.
|
|
|
|
const double selend = PositionToTime(event.m_x, rect.x);
|
2014-11-29 22:09:57 +00:00
|
|
|
wxInt64 pixelDist = 0;
|
|
|
|
SelectionBoundary boundary =
|
|
|
|
ChooseTimeBoundary(selend, onlyWithinSnapDistance,
|
|
|
|
&pixelDist, pPinValue);
|
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-10-25 22:05:45 +00:00
|
|
|
const double t0 = mViewInfo->selectedRegion.t0();
|
|
|
|
const double t1 = mViewInfo->selectedRegion.t1();
|
|
|
|
const double f0 = mViewInfo->selectedRegion.f0();
|
|
|
|
const double f1 = mViewInfo->selectedRegion.f1();
|
2014-11-08 14:30:19 +00:00
|
|
|
const double fc = mViewInfo->selectedRegion.fc();
|
2014-10-25 22:05:45 +00:00
|
|
|
double ratio = 0;
|
|
|
|
|
|
|
|
bool chooseTime = true;
|
|
|
|
bool chooseBottom = true;
|
|
|
|
bool chooseCenter = false;
|
|
|
|
bool logF;
|
|
|
|
// Consider adjustment of frequencies only if mouse is
|
|
|
|
// within the time boundaries
|
|
|
|
if (!mViewInfo->selectedRegion.isPoint() &&
|
|
|
|
t0 <= selend && selend < t1 &&
|
|
|
|
isSpectrogramTrack(pTrack, &logF)) {
|
|
|
|
const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
const wxInt64 bottomSel = (f0 >= 0)
|
|
|
|
? FrequencyToPosition(f0, rect.y, rect.height,
|
|
|
|
wt->GetRate(), logF)
|
|
|
|
: rect.y + rect.height;
|
|
|
|
const wxInt64 topSel = (f1 >= 0)
|
|
|
|
? FrequencyToPosition(f1, rect.y, rect.height,
|
2014-11-29 22:09:57 +00:00
|
|
|
wt->GetRate(), logF)
|
2014-10-25 22:05:45 +00:00
|
|
|
: rect.y;
|
2014-11-29 22:09:57 +00:00
|
|
|
wxInt64 signedBottomDist = int(event.m_y - bottomSel);
|
|
|
|
wxInt64 verticalDist = abs(signedBottomDist);
|
|
|
|
if (bottomSel == topSel)
|
|
|
|
// Top and bottom are too close to resolve on screen
|
|
|
|
chooseBottom = (signedBottomDist >= 0);
|
|
|
|
else {
|
|
|
|
const wxInt64 topDist = abs(int(event.m_y - topSel));
|
|
|
|
if (topDist < verticalDist)
|
|
|
|
chooseBottom = false, verticalDist = topDist;
|
|
|
|
}
|
2014-11-08 14:30:19 +00:00
|
|
|
if (fc > 0
|
|
|
|
#ifdef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
&& mayDragWidth
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
const wxInt64 centerSel =
|
|
|
|
FrequencyToPosition(fc, rect.y, rect.height,
|
|
|
|
wt->GetRate(), logF);
|
|
|
|
const wxInt64 centerDist = abs(int(event.m_y - centerSel));
|
|
|
|
if (centerDist < verticalDist)
|
|
|
|
chooseCenter = true, verticalDist = centerDist,
|
|
|
|
ratio = f1 / fc;
|
2014-10-25 22:05:45 +00:00
|
|
|
}
|
|
|
|
if (verticalDist >= 0 &&
|
2014-11-11 15:24:22 +00:00
|
|
|
verticalDist < pixelDist) {
|
2014-10-25 22:05:45 +00:00
|
|
|
pixelDist = verticalDist;
|
|
|
|
chooseTime = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chooseTime) {
|
|
|
|
// PRL: Seems I need a larger tolerance to make snapping work
|
|
|
|
// at top of track, not sure why
|
|
|
|
if (onlyWithinSnapDistance &&
|
|
|
|
pixelDist >= FREQ_SNAP_DISTANCE) {
|
|
|
|
SetIfNotNull( pPinValue, -1.0);
|
|
|
|
return SBNone;
|
|
|
|
}
|
|
|
|
else if (chooseCenter) {
|
|
|
|
SetIfNotNull( pPinValue, ratio);
|
|
|
|
return SBCenter;
|
|
|
|
}
|
2014-11-11 15:24:22 +00:00
|
|
|
else if (mayDragWidth && fc > 0) {
|
|
|
|
SetIfNotNull(pPinValue, fc);
|
|
|
|
return SBWidth;
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
else if (chooseBottom) {
|
2014-11-11 15:24:22 +00:00
|
|
|
SetIfNotNull( pPinValue, f1 );
|
2014-10-25 22:05:45 +00:00
|
|
|
return SBBottom;
|
|
|
|
}
|
|
|
|
else {
|
2014-11-11 15:24:22 +00:00
|
|
|
SetIfNotNull(pPinValue, f0);
|
2014-10-25 22:05:45 +00:00
|
|
|
return SBTop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
return boundary;
|
2014-10-25 22:05:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// HandleEnvelope gets called when the user is changing the
|
|
|
|
/// amplitude envelope on a track.
|
|
|
|
void TrackPanel::HandleEnvelope(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.LeftDown()) {
|
|
|
|
wxRect r;
|
|
|
|
mCapturedTrack = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
|
|
|
|
if (!mCapturedTrack)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (mCapturedTrack->GetKind() == Track::Wave)
|
|
|
|
{
|
|
|
|
mCapturedEnvelope =
|
|
|
|
((WaveTrack*)mCapturedTrack)->GetEnvelopeAtX(event.GetX());
|
|
|
|
} else {
|
|
|
|
mCapturedEnvelope = NULL;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedRect = r;
|
|
|
|
mCapturedRect.y += kTopInset;
|
|
|
|
mCapturedRect.height -= kTopInset;
|
|
|
|
}
|
|
|
|
// AS: if there's actually a selected track, then forward all of the
|
|
|
|
// mouse events to its envelope.
|
|
|
|
if (mCapturedTrack)
|
|
|
|
ForwardEventToEnvelope(event);
|
|
|
|
|
|
|
|
if (event.LeftUp()) {
|
|
|
|
mCapturedTrack = NULL;
|
|
|
|
MakeParentPushState(
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
|
2010-01-23 19:44:49 +00:00
|
|
|
_("Adjusted envelope."),
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: The envelope is a curve that controls the audio loudness.*/
|
2011-05-22 13:41:01 +00:00
|
|
|
_("Envelope")
|
2010-01-23 19:44:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// We've established we're a time track.
|
2010-01-23 19:44:49 +00:00
|
|
|
/// send events for its envelope.
|
|
|
|
void TrackPanel::ForwardEventToTimeTrackEnvelope(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
TimeTrack *ptimetrack = (TimeTrack *) mCapturedTrack;
|
|
|
|
Envelope *pspeedenvelope = ptimetrack->GetEnvelope();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect envRect = mCapturedRect;
|
|
|
|
envRect.y++;
|
|
|
|
envRect.height -= 2;
|
2012-12-19 21:49:25 +00:00
|
|
|
double lower = ptimetrack->GetRangeLower(), upper = ptimetrack->GetRangeUpper();
|
|
|
|
if(ptimetrack->GetDisplayLog()) {
|
|
|
|
// MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
|
|
|
|
double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE);
|
|
|
|
lower = 20.0 * log10(std::max(1.0e-7, lower)) / dBRange + 1.0;
|
|
|
|
upper = 20.0 * log10(std::max(1.0e-7, upper)) / dBRange + 1.0;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
bool needUpdate =
|
|
|
|
pspeedenvelope->MouseEvent(
|
|
|
|
event, envRect,
|
|
|
|
mViewInfo->h, mViewInfo->zoom,
|
2012-12-19 21:49:25 +00:00
|
|
|
ptimetrack->GetDisplayLog(), lower, upper);
|
2010-01-23 19:44:49 +00:00
|
|
|
if (needUpdate) {
|
|
|
|
RefreshTrack(mCapturedTrack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// We've established we're a wave track.
|
2010-01-23 19:44:49 +00:00
|
|
|
/// send events for its envelope.
|
|
|
|
void TrackPanel::ForwardEventToWaveTrackEnvelope(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
WaveTrack *pwavetrack = (WaveTrack *) mCapturedTrack;
|
|
|
|
Envelope *penvelope = mCapturedEnvelope;
|
|
|
|
|
|
|
|
// Possibly no-envelope, for example when in spectrum view mode.
|
|
|
|
// if so, then bail out.
|
|
|
|
if (!penvelope)
|
|
|
|
return;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// AS: WaveTracks can be displayed in several different formats.
|
|
|
|
// This asks which one is in use. (ie, Wave, Spectrum, etc)
|
|
|
|
int display = pwavetrack->GetDisplay();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// AS: If we're using the right type of display for envelope operations
|
|
|
|
// ie one of the Wave displays
|
|
|
|
if (display <= 1) {
|
|
|
|
bool dB = (display == 1);
|
|
|
|
bool needUpdate = false;
|
|
|
|
|
|
|
|
// AS: Then forward our mouse event to the envelope.
|
|
|
|
// It'll recalculate and then tell us whether or not to redraw.
|
|
|
|
wxRect envRect = mCapturedRect;
|
|
|
|
envRect.y++;
|
|
|
|
envRect.height -= 2;
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
needUpdate = penvelope->MouseEvent(
|
|
|
|
event, envRect,
|
|
|
|
mViewInfo->h, mViewInfo->zoom,
|
|
|
|
dB, zoomMin, zoomMax);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// If this track is linked to another track, make the identical
|
|
|
|
// change to the linked envelope:
|
|
|
|
WaveTrack *link = (WaveTrack *) mCapturedTrack->GetLink();
|
|
|
|
if (link) {
|
|
|
|
Envelope *e2 = link->GetEnvelopeAtX(event.GetX());
|
|
|
|
// There isn't necessarily an envelope there; no guarantee a
|
|
|
|
// linked track has the same WaveClip structure...
|
|
|
|
bool updateNeeded = false;
|
|
|
|
if (e2) {
|
|
|
|
wxRect envRect = mCapturedRect;
|
|
|
|
envRect.y++;
|
|
|
|
envRect.height -= 2;
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
updateNeeded = e2->MouseEvent(event, envRect,
|
|
|
|
mViewInfo->h, mViewInfo->zoom, dB,
|
|
|
|
zoomMin, zoomMax);
|
|
|
|
needUpdate |= updateNeeded;
|
|
|
|
}
|
|
|
|
if(!e2 || !updateNeeded) // no envelope found at this x point, or found but not updated
|
|
|
|
{
|
|
|
|
if( (e2 = link->GetActiveEnvelope()) != 0 ) // search for any active DragPoint
|
|
|
|
{
|
|
|
|
wxRect envRect = mCapturedRect;
|
|
|
|
envRect.y++;
|
|
|
|
envRect.height -= 2;
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
needUpdate |= e2->MouseEvent(event, envRect,
|
|
|
|
mViewInfo->h, mViewInfo->zoom, dB,
|
|
|
|
zoomMin, zoomMax);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needUpdate) {
|
|
|
|
RefreshTrack(mCapturedTrack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// The Envelope class actually handles things at the mouse
|
|
|
|
/// event level, so we have to forward the events over. Envelope
|
|
|
|
/// will then tell us whether or not we need to redraw.
|
|
|
|
|
|
|
|
// AS: I'm not sure why we can't let the Envelope take care of
|
|
|
|
// redrawing itself. ?
|
|
|
|
|
|
|
|
void TrackPanel::ForwardEventToEnvelope(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Time)
|
|
|
|
{
|
|
|
|
ForwardEventToTimeTrackEnvelope( event );
|
|
|
|
}
|
|
|
|
else if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Wave)
|
|
|
|
{
|
|
|
|
ForwardEventToWaveTrackEnvelope( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleSlide(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.LeftDown())
|
|
|
|
StartSlide(event);
|
|
|
|
|
|
|
|
if (mMouseCapture != IsSliding)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (event.Dragging() && mCapturedTrack)
|
|
|
|
DoSlide(event);
|
|
|
|
|
|
|
|
if (event.LeftUp()) {
|
2014-06-03 20:30:19 +00:00
|
|
|
if (mDidSlideVertically && mCapturedTrack)
|
|
|
|
// Now that user has dropped the clip into a different track,
|
2012-08-20 20:26:42 +00:00
|
|
|
// make sure the sample rate matches the destination track (mCapturedTrack).
|
2014-06-03 20:30:19 +00:00
|
|
|
for (size_t i = 0; i < mCapturedClipArray.GetCount(); i++)
|
2012-03-03 20:20:06 +00:00
|
|
|
if (mCapturedTrack->GetKind() == Track::Wave) // Should always be true here, but make sure.
|
|
|
|
{
|
2012-08-20 03:08:15 +00:00
|
|
|
WaveClip* pWaveClip = mCapturedClipArray[i].clip;
|
2014-06-03 20:30:19 +00:00
|
|
|
// Note that per TrackPanel::AddClipsToCaptured(Track *t, double t0, double t1),
|
|
|
|
// in the non-WaveTrack case, the code adds a NULL clip to mCapturedClipArray,
|
|
|
|
// so we have to check for that any time we're going to deref it.
|
2012-08-20 20:26:42 +00:00
|
|
|
// Previous code did not check it here, and that caused bug 367 crash.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pWaveClip)
|
2012-08-20 03:08:15 +00:00
|
|
|
{
|
2012-08-20 20:26:42 +00:00
|
|
|
pWaveClip->Resample(((WaveTrack*)mCapturedTrack)->GetRate());
|
2012-08-20 03:08:15 +00:00
|
|
|
pWaveClip->MarkChanged();
|
|
|
|
}
|
2012-03-03 20:20:06 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
|
|
|
|
if (mSnapManager) {
|
|
|
|
delete mSnapManager;
|
|
|
|
mSnapManager = NULL;
|
|
|
|
}
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
|
|
|
|
if (!mDidSlideVertically && mHSlideAmount==0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
|
|
|
|
wxString msg;
|
|
|
|
bool consolidate;
|
|
|
|
if (mDidSlideVertically) {
|
|
|
|
msg.Printf(_("Moved clip to another track"));
|
|
|
|
consolidate = false;
|
|
|
|
}
|
|
|
|
else {
|
2014-06-03 20:30:19 +00:00
|
|
|
wxString direction = mHSlideAmount>0 ?
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: a direction as in left or right.*/
|
2014-06-03 20:30:19 +00:00
|
|
|
_("right") :
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: a direction as in left or right.*/
|
|
|
|
_("left");
|
2010-01-23 19:44:49 +00:00
|
|
|
/* i18n-hint: %s is a direction like left or right */
|
|
|
|
msg.Printf(_("Time shifted tracks/clips %s %.02f seconds"),
|
|
|
|
direction.c_str(), fabs(mHSlideAmount));
|
|
|
|
consolidate = true;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
MakeParentPushState(msg, _("Time-Shift"),
|
2015-04-07 11:20:50 +00:00
|
|
|
consolidate ? (PUSH_CONSOLIDATE) : (PUSH_AUTOSAVE));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Prepare for sliding.
|
|
|
|
void TrackPanel::StartSlide(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
wxRect r;
|
|
|
|
|
|
|
|
mHSlideAmount = 0.0;
|
|
|
|
mDidSlideVertically = false;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
Track *vt = FindTrack(event.m_x, event.m_y, false, false, &r);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!vt)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
ToolsToolBar * ttb = mListener->TP_GetToolsToolBar();
|
|
|
|
bool multiToolModeActive = (ttb && ttb->IsDown(multiTool));
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
if ((vt->GetKind() == Track::Wave
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
|| vt->GetKind() == Track::Note
|
|
|
|
#endif
|
|
|
|
) && !event.ShiftDown())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (vt->GetKind() == Track::Wave) {
|
|
|
|
#endif
|
|
|
|
WaveTrack* wt = (WaveTrack*)vt;
|
|
|
|
mCapturedClip = wt->GetClipAtX(event.m_x);
|
|
|
|
if (mCapturedClip == NULL)
|
|
|
|
return;
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mCapturedClip = NULL;
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
// The captured clip is the focus, but we need to create a list
|
|
|
|
// of all clips that have to move, also...
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedClipArray.Clear();
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
double clickTime =
|
2010-01-23 19:44:49 +00:00
|
|
|
PositionToTime(event.m_x, GetLeftOffset());
|
|
|
|
bool clickedInSelection =
|
2010-09-18 21:02:36 +00:00
|
|
|
(vt->GetSelected() &&
|
2014-10-05 17:10:09 +00:00
|
|
|
clickTime > mViewInfo->selectedRegion.t0() &&
|
|
|
|
clickTime < mViewInfo->selectedRegion.t1());
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-03-06 18:39:31 +00:00
|
|
|
// First, if click was in selection, capture selected clips; otherwise
|
|
|
|
// just the clicked-on clip
|
2010-01-23 19:44:49 +00:00
|
|
|
if (clickedInSelection) {
|
|
|
|
mCapturedClipIsSelection = true;
|
|
|
|
|
2010-03-06 18:39:31 +00:00
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t->GetSelected()) {
|
2010-03-06 18:39:31 +00:00
|
|
|
AddClipsToCaptured(t, true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mCapturedClipIsSelection = false;
|
2010-09-18 21:02:36 +00:00
|
|
|
mCapturedClipArray.Add(TrackClip(vt, mCapturedClip));
|
2010-03-06 18:39:31 +00:00
|
|
|
|
|
|
|
// Check for stereo partner
|
2010-09-18 21:02:36 +00:00
|
|
|
Track *partner = mTracks->GetLink(vt);
|
2011-04-25 18:10:32 +00:00
|
|
|
if (mCapturedClip && partner && partner->GetKind() == Track::Wave) {
|
|
|
|
// WaveClip::GetClipAtX doesn't work unless the clip is on the screen and can return bad info otherwise
|
2011-04-26 16:47:06 +00:00
|
|
|
// instead calculate the time manually
|
|
|
|
double rate = ((WaveTrack*)partner)->GetRate();
|
|
|
|
double pps = mViewInfo->zoom;
|
|
|
|
double tt = (event.m_x - GetLeftOffset()) / pps + mViewInfo->h;
|
|
|
|
sampleCount s0 = (sampleCount)(tt * rate + 0.5);
|
|
|
|
|
|
|
|
if (s0 >= 0) {
|
|
|
|
WaveClip *clip = ((WaveTrack *)partner)->GetClipAtSample(s0);
|
2011-04-25 18:10:32 +00:00
|
|
|
if (clip) {
|
|
|
|
mCapturedClipArray.Add(TrackClip(partner, clip));
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2010-03-06 18:39:31 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2012-08-20 20:26:42 +00:00
|
|
|
// Now, if sync-lock is enabled, capture any clip that's linked to a
|
|
|
|
// captured clip.
|
2010-08-11 22:47:26 +00:00
|
|
|
if (GetProject()->IsSyncLocked()) {
|
2010-03-06 18:39:31 +00:00
|
|
|
// AWD: mCapturedClipArray expands as the loop runs, so newly-added
|
|
|
|
// clips are considered (the effect is like recursion and terminates
|
2012-08-20 20:26:42 +00:00
|
|
|
// because AddClipsToCaptured doesn't add duplicate clips); to remove
|
2010-03-06 18:39:31 +00:00
|
|
|
// this behavior just store the array size beforehand.
|
|
|
|
for (unsigned int i = 0; i < mCapturedClipArray.GetCount(); ++i) {
|
2010-09-18 21:02:36 +00:00
|
|
|
// Capture based on tracks that have clips -- that means we
|
2010-03-06 18:39:31 +00:00
|
|
|
// don't capture based on links to label tracks for now (until
|
|
|
|
// we can treat individual labels as clips)
|
|
|
|
if (mCapturedClipArray[i].clip) {
|
2011-04-18 06:30:07 +00:00
|
|
|
// Iterate over sync-lock group tracks.
|
2010-08-11 23:56:50 +00:00
|
|
|
SyncLockedTracksIterator git(mTracks);
|
2010-03-06 18:39:31 +00:00
|
|
|
for ( Track *t = git.First(mCapturedClipArray[i].track);
|
|
|
|
t; t = git.Next() )
|
2013-10-29 21:49:45 +00:00
|
|
|
{
|
2010-03-06 18:39:31 +00:00
|
|
|
AddClipsToCaptured(t,
|
|
|
|
mCapturedClipArray[i].clip->GetStartTime(),
|
|
|
|
mCapturedClipArray[i].clip->GetEndTime() );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
// Capture additional clips from NoteTracks
|
|
|
|
Track *nt = mCapturedClipArray[i].track;
|
|
|
|
if (nt->GetKind() == Track::Note) {
|
2011-04-18 06:30:07 +00:00
|
|
|
// Iterate over sync-lock group tracks.
|
2010-09-18 21:02:36 +00:00
|
|
|
SyncLockedTracksIterator git(mTracks);
|
|
|
|
for (Track *t = git.First(nt); t; t = git.Next())
|
|
|
|
{
|
|
|
|
AddClipsToCaptured(t, nt->GetStartTime(), nt->GetEndTime());
|
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2010-03-06 18:39:31 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
} else {
|
|
|
|
mCapturedClip = NULL;
|
|
|
|
mCapturedClipArray.Clear();
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive;
|
|
|
|
|
|
|
|
mCapturedTrack = vt;
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
mMouseClickX = event.m_x;
|
|
|
|
mMouseClickY = event.m_y;
|
|
|
|
|
2014-11-29 17:10:56 +00:00
|
|
|
mSelStartValid = true;
|
2010-01-23 19:44:49 +00:00
|
|
|
mSelStart = mViewInfo->h + ((event.m_x - r.x) / mViewInfo->zoom);
|
|
|
|
|
|
|
|
if (mSnapManager)
|
|
|
|
delete mSnapManager;
|
|
|
|
mSnapManager = new SnapManager(mTracks,
|
|
|
|
&mCapturedClipArray,
|
|
|
|
mViewInfo->zoom,
|
2010-02-02 19:43:52 +00:00
|
|
|
4, // pixel tolerance
|
|
|
|
true); // don't snap to time
|
2010-01-23 19:44:49 +00:00
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
mSnapPreferRightEdge = false;
|
|
|
|
if (mCapturedClip) {
|
|
|
|
if (fabs(mSelStart - mCapturedClip->GetEndTime()) <
|
|
|
|
fabs(mSelStart - mCapturedClip->GetStartTime()))
|
|
|
|
mSnapPreferRightEdge = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
mMouseCapture = IsSliding;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper for the above, adds a track's clips to mCapturedClipArray (eliminates
|
|
|
|
// duplication of this logic)
|
|
|
|
void TrackPanel::AddClipsToCaptured(Track *t, bool withinSelection)
|
2010-03-06 18:39:31 +00:00
|
|
|
{
|
|
|
|
if (withinSelection)
|
2014-10-05 17:10:09 +00:00
|
|
|
AddClipsToCaptured(t, mViewInfo->selectedRegion.t0(),
|
|
|
|
mViewInfo->selectedRegion.t1());
|
2010-03-06 18:39:31 +00:00
|
|
|
else
|
|
|
|
AddClipsToCaptured(t, t->GetStartTime(), t->GetEndTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adds a track's clips to mCapturedClipArray within a specified time
|
|
|
|
void TrackPanel::AddClipsToCaptured(Track *t, double t0, double t1)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (t->GetKind() == Track::Wave)
|
|
|
|
{
|
|
|
|
WaveClipList::compatibility_iterator it =
|
|
|
|
((WaveTrack *)t)->GetClipIterator();
|
|
|
|
while (it)
|
|
|
|
{
|
|
|
|
WaveClip *clip = it->GetData();
|
2010-03-06 18:39:31 +00:00
|
|
|
|
|
|
|
if ( ! clip->AfterClip(t0) && ! clip->BeforeClip(t1) )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-03-06 18:39:31 +00:00
|
|
|
// Avoid getting clips that were already captured
|
|
|
|
bool newClip = true;
|
|
|
|
for (unsigned int i = 0; i < mCapturedClipArray.GetCount(); ++i) {
|
|
|
|
if (mCapturedClipArray[i].clip == clip) {
|
|
|
|
newClip = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newClip)
|
|
|
|
mCapturedClipArray.Add(TrackClip(t, clip));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
it = it->GetNext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This handles label tracks rather heavy-handedly -- it would be nice to
|
|
|
|
// treat individual labels like clips
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-03-06 18:39:31 +00:00
|
|
|
// Avoid adding a track twice
|
|
|
|
bool newClip = true;
|
|
|
|
for (unsigned int i = 0; i < mCapturedClipArray.GetCount(); ++i) {
|
|
|
|
if (mCapturedClipArray[i].track == t) {
|
|
|
|
newClip = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
if (newClip) {
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
// do not add NoteTrack if the data is outside of time bounds
|
|
|
|
if (t->GetKind() == Track::Note) {
|
2014-06-03 20:30:19 +00:00
|
|
|
if (t->GetEndTime() < t0 || t->GetStartTime() > t1)
|
2010-09-18 21:02:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2010-03-06 18:39:31 +00:00
|
|
|
mCapturedClipArray.Add(TrackClip(t, NULL));
|
2010-09-18 21:02:36 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Slide tracks horizontally, or slide clips horizontally or vertically
|
|
|
|
/// (e.g. moving clips between tracks).
|
|
|
|
|
|
|
|
// GM: DoSlide now implementing snap-to
|
2014-06-03 20:30:19 +00:00
|
|
|
// samples functionality based on sample rate.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::DoSlide(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
// find which track the mouse is currently in (mouseTrack) -
|
|
|
|
// this may not be the same as the one we started in...
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
Track *mouseTrack = FindTrack(event.m_x, event.m_y, false, false, NULL);
|
|
|
|
if (!mouseTrack || (mouseTrack->GetKind() != Track::Wave &&
|
|
|
|
mouseTrack->GetKind() != Track::Note)) {
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack *mouseTrack =
|
|
|
|
(WaveTrack *)FindTrack(event.m_x, event.m_y, false, false, NULL);
|
|
|
|
if (!mouseTrack || mouseTrack->GetKind() != Track::Wave) {
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start by undoing the current slide amount; everything
|
|
|
|
// happens relative to the original horizontal position of
|
|
|
|
// each clip...
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (mCapturedClipArray.GetCount()) {
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCapturedClip) {
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
for(i=0; i<mCapturedClipArray.GetCount(); i++) {
|
|
|
|
if (mCapturedClipArray[i].clip)
|
|
|
|
mCapturedClipArray[i].clip->Offset(-mHSlideAmount);
|
|
|
|
else
|
|
|
|
mCapturedClipArray[i].track->Offset(-mHSlideAmount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mCapturedTrack->Offset(-mHSlideAmount);
|
|
|
|
Track* link = mTracks->GetLink(mCapturedTrack);
|
|
|
|
if (link)
|
|
|
|
link->Offset(-mHSlideAmount);
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCapturedClipIsSelection) {
|
|
|
|
// Slide the selection, too
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.move(-mHSlideAmount);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
mHSlideAmount = 0.0;
|
|
|
|
|
|
|
|
double desiredSlideAmount = (event.m_x - mMouseClickX) / mViewInfo->zoom;
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (mouseTrack->GetKind() == Track::Wave) {
|
|
|
|
WaveTrack *mtw = (WaveTrack *) mouseTrack;
|
2014-06-03 20:30:19 +00:00
|
|
|
desiredSlideAmount = rint(mtw->GetRate() * desiredSlideAmount) /
|
2010-09-18 21:02:36 +00:00
|
|
|
mtw->GetRate(); // set it to a sample point
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
// Adjust desiredSlideAmount using SnapManager
|
2010-09-18 21:02:36 +00:00
|
|
|
if (mSnapManager && mCapturedClipArray.GetCount()) {
|
2014-06-03 20:30:19 +00:00
|
|
|
double clipLeft;
|
2010-09-18 21:02:36 +00:00
|
|
|
double clipRight;
|
|
|
|
if (mCapturedClip) {
|
|
|
|
clipLeft = mCapturedClip->GetStartTime() + desiredSlideAmount;
|
|
|
|
clipRight = mCapturedClip->GetEndTime() + desiredSlideAmount;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount;
|
|
|
|
clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount;
|
|
|
|
}
|
|
|
|
#else
|
2014-06-03 20:30:19 +00:00
|
|
|
desiredSlideAmount = rint(mouseTrack->GetRate() * desiredSlideAmount) /
|
2010-09-18 21:02:36 +00:00
|
|
|
mouseTrack->GetRate(); // set it to a sample point
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mSnapManager && mCapturedClip) {
|
|
|
|
double clipLeft = mCapturedClip->GetStartTime() + desiredSlideAmount;
|
|
|
|
double clipRight = mCapturedClip->GetEndTime() + desiredSlideAmount;
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
double newClipLeft = clipLeft;
|
|
|
|
double newClipRight = clipRight;
|
|
|
|
|
2010-02-02 19:43:52 +00:00
|
|
|
bool dummy1, dummy2;
|
|
|
|
mSnapManager->Snap(mCapturedTrack, clipLeft, false, &newClipLeft,
|
|
|
|
&dummy1, &dummy2);
|
|
|
|
mSnapManager->Snap(mCapturedTrack, clipRight, false, &newClipRight,
|
|
|
|
&dummy1, &dummy2);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Only one of them is allowed to snap
|
|
|
|
if (newClipLeft != clipLeft && newClipRight != clipRight) {
|
|
|
|
if (mSnapPreferRightEdge)
|
|
|
|
newClipLeft = clipLeft;
|
|
|
|
else
|
|
|
|
newClipRight = clipRight;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Take whichever one snapped (if any) and compute the new desiredSlideAmount
|
|
|
|
mSnapLeft = -1;
|
|
|
|
mSnapRight = -1;
|
|
|
|
if (newClipLeft != clipLeft) {
|
|
|
|
double difference = (newClipLeft - clipLeft);
|
|
|
|
desiredSlideAmount += difference;
|
|
|
|
mSnapLeft = TimeToPosition(newClipLeft, GetLeftOffset());
|
|
|
|
}
|
|
|
|
else if (newClipRight != clipRight) {
|
|
|
|
double difference = (newClipRight - clipRight);
|
|
|
|
desiredSlideAmount += difference;
|
|
|
|
mSnapRight = TimeToPosition(newClipRight, GetLeftOffset());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement sliding within the track(s)
|
|
|
|
if (mSlideUpDownOnly) {
|
|
|
|
desiredSlideAmount = 0.0;
|
|
|
|
}
|
|
|
|
|
2012-08-20 21:55:45 +00:00
|
|
|
// Scroll during vertical drag.
|
2012-08-21 02:53:46 +00:00
|
|
|
// EnsureVisible(mouseTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2.
|
2012-08-20 21:55:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//If the mouse is over a track that isn't the captured track,
|
|
|
|
//drag the clip to the mousetrack
|
2012-05-05 15:27:51 +00:00
|
|
|
if (mCapturedClip && mouseTrack != mCapturedTrack /*&&
|
|
|
|
!mCapturedClipIsSelection*/)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Make sure we always have the first linked track of a stereo track
|
|
|
|
if (!mouseTrack->GetLinked() && mTracks->GetLink(mouseTrack))
|
2014-06-03 20:30:19 +00:00
|
|
|
mouseTrack =
|
2012-07-18 03:44:34 +00:00
|
|
|
#ifndef USE_MIDI
|
|
|
|
(WaveTrack *)
|
|
|
|
#endif
|
|
|
|
mTracks->GetLink(mouseTrack);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Temporary apply the offset because we want to see if the
|
|
|
|
// track fits with the desired offset
|
|
|
|
for(i=0; i<mCapturedClipArray.GetCount(); i++)
|
|
|
|
if (mCapturedClipArray[i].clip)
|
|
|
|
mCapturedClipArray[i].clip->Offset(desiredSlideAmount);
|
|
|
|
// See if it can be moved
|
2014-06-03 20:30:19 +00:00
|
|
|
if (MoveClipToTrack(mCapturedClip,
|
2010-09-18 21:02:36 +00:00
|
|
|
(WaveTrack*)mouseTrack)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedTrack = mouseTrack;
|
|
|
|
mDidSlideVertically = true;
|
|
|
|
|
2012-05-05 15:27:51 +00:00
|
|
|
if (mCapturedClipIsSelection) {
|
|
|
|
// Slide the selection, too
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.move(desiredSlideAmount);
|
2012-05-05 15:27:51 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Make the offset permanent; start from a "clean slate"
|
|
|
|
mHSlideAmount = 0.0;
|
|
|
|
desiredSlideAmount = 0.0;
|
|
|
|
mMouseClickX = event.m_x;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Undo the offset
|
|
|
|
for(i=0; i<mCapturedClipArray.GetCount(); i++)
|
|
|
|
if (mCapturedClipArray[i].clip)
|
|
|
|
mCapturedClipArray[i].clip->Offset(-desiredSlideAmount);
|
|
|
|
}
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement sliding within the track(s)
|
|
|
|
if (mSlideUpDownOnly)
|
|
|
|
return;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Determine desired amount to slide
|
|
|
|
mHSlideAmount = desiredSlideAmount;
|
|
|
|
|
|
|
|
if (mHSlideAmount == 0.0) {
|
|
|
|
Refresh(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
if (mCapturedClipArray.GetCount()) {
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCapturedClip) {
|
2010-09-18 21:02:36 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
double allowed;
|
|
|
|
double initialAllowed;
|
|
|
|
double safeBigDistance = 1000 + 2.0 * (mTracks->GetEndTime() -
|
|
|
|
mTracks->GetStartTime());
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
do { // loop to compute allowed, does not actually move anything yet
|
2010-01-23 19:44:49 +00:00
|
|
|
initialAllowed = mHSlideAmount;
|
|
|
|
|
|
|
|
unsigned int i, j;
|
|
|
|
for(i=0; i<mCapturedClipArray.GetCount(); i++) {
|
|
|
|
WaveTrack *track = (WaveTrack *)mCapturedClipArray[i].track;
|
|
|
|
WaveClip *clip = mCapturedClipArray[i].clip;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
if (clip) { // only audio clips are used to compute allowed
|
2010-01-23 19:44:49 +00:00
|
|
|
// Move all other selected clips totally out of the way
|
|
|
|
// temporarily because they're all moving together and
|
|
|
|
// we want to find out if OTHER clips are in the way,
|
|
|
|
// not one of the moving ones
|
|
|
|
for(j=0; j<mCapturedClipArray.GetCount(); j++) {
|
|
|
|
WaveClip *clip2 = mCapturedClipArray[j].clip;
|
|
|
|
if (clip2 && clip2 != clip)
|
|
|
|
clip2->Offset(-safeBigDistance);
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (track->CanOffsetClip(clip, mHSlideAmount, &allowed)) {
|
|
|
|
mHSlideAmount = allowed;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mHSlideAmount = 0.0;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for(j=0; j<mCapturedClipArray.GetCount(); j++) {
|
|
|
|
WaveClip *clip2 = mCapturedClipArray[j].clip;
|
|
|
|
if (clip2 && clip2 != clip)
|
|
|
|
clip2->Offset(safeBigDistance);
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
} while (mHSlideAmount != initialAllowed);
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
if (mHSlideAmount != 0.0) { // finally, here is where clips are moved
|
2010-01-23 19:44:49 +00:00
|
|
|
unsigned int i;
|
|
|
|
for(i=0; i<mCapturedClipArray.GetCount(); i++) {
|
|
|
|
Track *track = mCapturedClipArray[i].track;
|
|
|
|
WaveClip *clip = mCapturedClipArray[i].clip;
|
|
|
|
if (clip)
|
|
|
|
clip->Offset(mHSlideAmount);
|
|
|
|
else
|
|
|
|
track->Offset(mHSlideAmount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2010-09-18 21:02:36 +00:00
|
|
|
// For non wavetracks, specifically label tracks ...
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedTrack->Offset(mHSlideAmount);
|
|
|
|
Track* link = mTracks->GetLink(mCapturedTrack);
|
|
|
|
if (link)
|
|
|
|
link->Offset(mHSlideAmount);
|
|
|
|
}
|
|
|
|
if (mCapturedClipIsSelection) {
|
|
|
|
// Slide the selection, too
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.move(mHSlideAmount);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// This method takes care of our different zoom
|
2010-01-23 19:44:49 +00:00
|
|
|
/// possibilities. It is possible for a user to just
|
2014-06-03 20:30:19 +00:00
|
|
|
/// "zoom in" or "zoom out," but it is also possible
|
2010-01-23 19:44:49 +00:00
|
|
|
/// for a user to drag and select an area that he
|
|
|
|
/// or she wants to be zoomed in on. We use mZoomStart
|
|
|
|
/// and mZoomEnd to track the beggining and end of such
|
|
|
|
/// a zoom area. Note that the ViewInfo member
|
|
|
|
/// mViewInfo actually keeps track of our zoom constant,
|
|
|
|
/// so we achieve zooming by altering the zoom constant
|
|
|
|
/// and forcing a refresh.
|
|
|
|
void TrackPanel::HandleZoom(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.ButtonDown() || event.LeftDClick()) {
|
|
|
|
HandleZoomClick(event);
|
|
|
|
}
|
|
|
|
else if (mMouseCapture == IsZooming) {
|
|
|
|
if (event.Dragging()) {
|
|
|
|
HandleZoomDrag(event);
|
|
|
|
}
|
|
|
|
else if (event.ButtonUp()) {
|
|
|
|
HandleZoomButtonUp(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Zoom button down, record the position.
|
|
|
|
void TrackPanel::HandleZoomClick(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (mCapturedTrack)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mCapturedTrack = FindTrack(event.m_x, event.m_y, false, false,
|
|
|
|
&mCapturedRect);
|
|
|
|
if (!mCapturedTrack)
|
|
|
|
return;
|
|
|
|
|
|
|
|
SetCapturedTrack(mCapturedTrack, IsZooming);
|
|
|
|
|
|
|
|
mZoomStart = event.m_x;
|
|
|
|
mZoomEnd = event.m_x;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Zoom drag
|
|
|
|
void TrackPanel::HandleZoomDrag(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
int left, width, height;
|
|
|
|
|
|
|
|
left = GetLeftOffset();
|
|
|
|
GetTracksUsableArea(&width, &height);
|
|
|
|
|
|
|
|
mZoomEnd = event.m_x;
|
|
|
|
|
|
|
|
if (event.m_x < left) {
|
|
|
|
mZoomEnd = left;
|
|
|
|
}
|
|
|
|
else if (event.m_x >= left + width - 1) {
|
|
|
|
mZoomEnd = left + width - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsDragZooming()) {
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Zoom button up
|
|
|
|
void TrackPanel::HandleZoomButtonUp(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (mZoomEnd < mZoomStart) {
|
|
|
|
int temp = mZoomEnd;
|
|
|
|
mZoomEnd = mZoomStart;
|
|
|
|
mZoomStart = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsDragZooming())
|
|
|
|
DragZoom(event, GetLeftOffset());
|
|
|
|
else
|
|
|
|
DoZoomInOut(event, GetLeftOffset());
|
|
|
|
|
|
|
|
mZoomEnd = mZoomStart = 0;
|
|
|
|
|
|
|
|
SetCapturedTrack(NULL);
|
|
|
|
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines if drag zooming is active
|
|
|
|
bool TrackPanel::IsDragZooming()
|
|
|
|
{
|
|
|
|
return (abs(mZoomEnd - mZoomStart) > DragThreshold);
|
|
|
|
}
|
|
|
|
|
2013-02-22 21:29:19 +00:00
|
|
|
/// Determines if the a modal tool is active
|
2012-12-04 16:41:43 +00:00
|
|
|
bool TrackPanel::IsMouseCaptured()
|
|
|
|
{
|
2014-12-06 12:28:10 +00:00
|
|
|
return (mMouseCapture != IsUncaptured || mCapturedTrack != NULL);
|
2012-12-04 16:41:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// This actually sets the Zoom value when you're done doing
|
|
|
|
/// a drag zoom.
|
|
|
|
void TrackPanel::DragZoom(wxMouseEvent & event, int trackLeftEdge)
|
|
|
|
{
|
|
|
|
double left = PositionToTime(mZoomStart, trackLeftEdge);
|
|
|
|
double right = PositionToTime(mZoomEnd, trackLeftEdge);
|
|
|
|
|
|
|
|
if (event.ShiftDown())
|
|
|
|
mViewInfo->zoom /= mViewInfo->screen / (right - left);
|
|
|
|
else
|
|
|
|
mViewInfo->zoom *= mViewInfo->screen / (right - left);
|
|
|
|
|
|
|
|
if (mViewInfo->zoom > gMaxZoom)
|
|
|
|
mViewInfo->zoom = gMaxZoom;
|
|
|
|
if (mViewInfo->zoom <= gMinZoom)
|
|
|
|
mViewInfo->zoom = gMinZoom;
|
|
|
|
|
|
|
|
mViewInfo->h = left;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This handles normal Zoom In/Out, if you just clicked;
|
|
|
|
/// IOW, if you were NOT dragging to zoom an area.
|
2010-09-12 05:11:43 +00:00
|
|
|
/// \todo MAGIC NUMBER: We've got several in this method.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::DoZoomInOut(wxMouseEvent & event, int trackLeftEdge)
|
|
|
|
{
|
|
|
|
double center_h = PositionToTime(event.m_x, trackLeftEdge);
|
|
|
|
|
|
|
|
if (event.RightUp() || event.RightDClick() || event.ShiftDown())
|
|
|
|
mViewInfo->zoom = wxMax(mViewInfo->zoom / 2.0, gMinZoom);
|
|
|
|
else
|
|
|
|
mViewInfo->zoom = wxMin(mViewInfo->zoom * 2.0, gMaxZoom);
|
|
|
|
|
|
|
|
if (event.MiddleUp() || event.MiddleDClick())
|
|
|
|
mViewInfo->zoom = 44100.0 / 512.0; // AS: Reset zoom.
|
|
|
|
|
|
|
|
double new_center_h = PositionToTime(event.m_x, trackLeftEdge);
|
|
|
|
|
|
|
|
mViewInfo->h += (center_h - new_center_h);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Vertical zooming (triggered by clicking in the
|
|
|
|
/// vertical ruler)
|
|
|
|
void TrackPanel::HandleVZoom(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.ButtonDown() || event.ButtonDClick()) {
|
|
|
|
HandleVZoomClick( event );
|
|
|
|
}
|
|
|
|
else if (event.Dragging()) {
|
|
|
|
HandleVZoomDrag( event );
|
|
|
|
}
|
|
|
|
else if (event.ButtonUp()) {
|
|
|
|
HandleVZoomButtonUp( event );
|
|
|
|
}
|
2012-12-19 21:49:25 +00:00
|
|
|
//TODO-MB: add timetrack zooming here!
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// VZoom click
|
|
|
|
void TrackPanel::HandleVZoomClick( wxMouseEvent & event )
|
|
|
|
{
|
|
|
|
if (mCapturedTrack)
|
|
|
|
return;
|
|
|
|
mCapturedTrack = FindTrack(event.m_x, event.m_y, true, false,
|
|
|
|
&mCapturedRect);
|
|
|
|
if (!mCapturedTrack)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// don't do anything if track is not wave or Spectrum/log Spectrum
|
2014-06-03 20:30:19 +00:00
|
|
|
if (((mCapturedTrack->GetKind() == Track::Wave) &&
|
2011-10-19 23:06:53 +00:00
|
|
|
(((WaveTrack*)mCapturedTrack)->GetDisplay() <= WaveTrack::SpectrumLogDisplay))
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef USE_MIDI
|
2014-06-03 20:30:19 +00:00
|
|
|
|| mCapturedTrack->GetKind() == Track::Note
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2014-06-03 20:30:19 +00:00
|
|
|
)
|
2011-10-19 23:06:53 +00:00
|
|
|
{
|
2010-09-18 21:02:36 +00:00
|
|
|
mMouseCapture = IsVZooming;
|
|
|
|
mZoomStart = event.m_y;
|
|
|
|
mZoomEnd = event.m_y;
|
|
|
|
// change note track to zoom like audio track
|
2011-10-19 23:06:53 +00:00
|
|
|
//#ifdef USE_MIDI
|
|
|
|
// if (mCapturedTrack->GetKind() == Track::Note) {
|
|
|
|
// ((NoteTrack *) mCapturedTrack)->StartVScroll();
|
|
|
|
// }
|
|
|
|
//#endif
|
2010-09-18 21:02:36 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// VZoom drag
|
|
|
|
void TrackPanel::HandleVZoomDrag( wxMouseEvent & event )
|
|
|
|
{
|
|
|
|
mZoomEnd = event.m_y;
|
|
|
|
if (IsDragZooming()){
|
2010-09-18 21:02:36 +00:00
|
|
|
// changed Note track to work like audio track
|
|
|
|
//#ifdef USE_MIDI
|
|
|
|
// if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Note) {
|
|
|
|
// ((NoteTrack *) mCapturedTrack)->VScroll(mZoomStart, mZoomEnd);
|
|
|
|
// }
|
|
|
|
//#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// VZoom Button up.
|
|
|
|
/// There are three cases:
|
|
|
|
/// - Drag-zooming; we already have a min and max
|
|
|
|
/// - Zoom out; ensure we don't go too small.
|
|
|
|
/// - Zoom in; ensure we don't go too large.
|
|
|
|
void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event )
|
|
|
|
{
|
2011-02-07 20:24:04 +00:00
|
|
|
int minBins = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!mCapturedTrack)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mMouseCapture = IsUncaptured;
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
// handle vertical scrolling in Note Track. This is so different from
|
|
|
|
// zooming in audio tracks that it is handled as a special case from
|
|
|
|
// which we then return
|
|
|
|
if (mCapturedTrack->GetKind() == Track::Note) {
|
|
|
|
NoteTrack *nt = (NoteTrack *) mCapturedTrack;
|
|
|
|
if (IsDragZooming()) {
|
|
|
|
nt->ZoomTo(mZoomStart, mZoomEnd);
|
|
|
|
} else if (event.ShiftDown() || event.RightUp()) {
|
|
|
|
nt->ZoomOut(mZoomEnd);
|
|
|
|
} else {
|
|
|
|
nt->ZoomIn(mZoomEnd);
|
|
|
|
}
|
|
|
|
mZoomEnd = mZoomStart = 0;
|
|
|
|
Refresh(false);
|
|
|
|
mCapturedTrack = NULL;
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-09-18 21:02:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// don't do anything if track is not wave
|
|
|
|
if (mCapturedTrack->GetKind() != Track::Wave)
|
|
|
|
return;
|
|
|
|
WaveTrack *track = (WaveTrack *)mCapturedTrack;
|
|
|
|
WaveTrack *partner = (WaveTrack *)mTracks->GetLink(track);
|
|
|
|
int height = track->GetHeight();
|
|
|
|
int ypos = mCapturedRect.y;
|
|
|
|
|
|
|
|
// Ensure start and end are in order (swap if not).
|
|
|
|
if (mZoomEnd < mZoomStart) {
|
|
|
|
int temp = mZoomEnd;
|
|
|
|
mZoomEnd = mZoomStart;
|
|
|
|
mZoomStart = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
float min, max, c, l, binSize = 0.0;
|
|
|
|
bool spectrum, spectrumLog;
|
2014-06-03 20:30:19 +00:00
|
|
|
int windowSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
2014-06-03 20:30:19 +00:00
|
|
|
int fftSkipPoints=0;
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
double rate = ((WaveTrack *)track)->GetRate();
|
2010-09-18 21:02:36 +00:00
|
|
|
spectrum = ((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumDisplay;
|
2010-01-23 19:44:49 +00:00
|
|
|
spectrumLog=(((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumLogDisplay);
|
|
|
|
if(spectrum) {
|
|
|
|
min = mTrackArtist->GetSpectrumMinFreq(0);
|
|
|
|
if(min < 0)
|
|
|
|
min = 0;
|
|
|
|
max = mTrackArtist->GetSpectrumMaxFreq(8000);
|
|
|
|
if(max > rate/2.)
|
|
|
|
max = rate/2.;
|
|
|
|
windowSize = mTrackArtist->GetSpectrumWindowSize();
|
|
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints();
|
|
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
binSize = rate / windowSize;
|
|
|
|
minBins = wxMin(10, windowSize/2); //minimum 10 freq bins, unless there are less
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if(spectrumLog) {
|
|
|
|
min = mTrackArtist->GetSpectrumLogMinFreq(lrint(rate/1000.0));
|
|
|
|
if(min < 1)
|
|
|
|
min = 1;
|
|
|
|
max = mTrackArtist->GetSpectrumLogMaxFreq(lrint(rate/2.));
|
|
|
|
if(max > rate/2.)
|
|
|
|
max = rate/2.;
|
|
|
|
windowSize = mTrackArtist->GetSpectrumWindowSize();
|
|
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints();
|
|
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
binSize = rate / windowSize;
|
|
|
|
minBins = wxMin(10, windowSize/2); //minimum 10 freq bins, unless there are less
|
|
|
|
}
|
|
|
|
else
|
|
|
|
track->GetDisplayBounds(&min, &max);
|
|
|
|
if (IsDragZooming()) {
|
|
|
|
// Drag Zoom
|
|
|
|
float p1, p2, tmin, tmax;
|
|
|
|
tmin=min;
|
|
|
|
tmax=max;
|
|
|
|
|
|
|
|
if(spectrumLog) {
|
|
|
|
double xmin = 1-(mZoomEnd - ypos) / (float)height;
|
|
|
|
double xmax = 1-(mZoomStart - ypos) / (float)height;
|
|
|
|
double lmin=log10(tmin), lmax=log10(tmax);
|
|
|
|
double d=lmax-lmin;
|
|
|
|
min=wxMax(1.0, pow(10, xmin*d+lmin));
|
|
|
|
max=wxMin(rate/2.0, pow(10, xmax*d+lmin));
|
|
|
|
// Enforce vertical zoom limits
|
|
|
|
// done in the linear freq domain for now, but not too far out
|
|
|
|
if(max < min + minBins * binSize)
|
|
|
|
max = min + minBins * binSize;
|
|
|
|
if(max > rate/2.) {
|
|
|
|
max = rate/2.;
|
|
|
|
min = max - minBins * binSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
p1 = (mZoomStart - ypos) / (float)height;
|
|
|
|
p2 = (mZoomEnd - ypos) / (float)height;
|
|
|
|
max = (tmax * (1.0-p1) + tmin * p1);
|
|
|
|
min = (tmax * (1.0-p2) + tmin * p2);
|
|
|
|
|
|
|
|
// Enforce vertical zoom limits
|
|
|
|
if(spectrum) {
|
|
|
|
if(min < 0.)
|
|
|
|
min = 0.;
|
|
|
|
if(max < min + minBins * binSize)
|
|
|
|
max = min + minBins * binSize;
|
|
|
|
if(max > rate/2.) {
|
|
|
|
max = rate/2.;
|
|
|
|
min = max - minBins * binSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2013-11-28 01:11:09 +00:00
|
|
|
// Waveform view - allow zooming down to a range of ZOOMLIMIT
|
|
|
|
if (max - min < ZOOMLIMIT) { // if user attempts to go smaller...
|
|
|
|
c = (min+max)/2; // ...set centre of view to centre of dragged area and top/bottom to ZOOMLIMIT/2 above/below
|
|
|
|
min = c - ZOOMLIMIT/2.0;
|
|
|
|
max = c + ZOOMLIMIT/2.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (event.ShiftDown() || event.RightUp()) {
|
|
|
|
// Zoom OUT
|
|
|
|
// Zoom out to -1.0...1.0 first, then, and only
|
|
|
|
// then, if they click again, allow one more
|
|
|
|
// zoom out.
|
|
|
|
if(spectrum) {
|
2013-11-28 01:11:09 +00:00
|
|
|
if (event.ShiftDown() && event.RightUp()) {
|
2014-11-08 14:30:19 +00:00
|
|
|
// Zoom out full
|
|
|
|
min = 0.0;
|
|
|
|
max = rate/2.;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
else {
|
2013-11-28 01:11:09 +00:00
|
|
|
// Zoom out
|
|
|
|
c = 0.5*(min+max);
|
|
|
|
l = (c - min);
|
|
|
|
if(c - 2*l <= 0) {
|
|
|
|
min = 0.0;
|
|
|
|
max = wxMin( rate/2., 2. * max);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
min = wxMax( 0.0, c - 2*l);
|
|
|
|
max = wxMin( rate/2., c + 2*l);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2013-11-28 01:11:09 +00:00
|
|
|
if(spectrumLog) {
|
|
|
|
if (event.ShiftDown() && event.RightUp()) {
|
|
|
|
// Zoom out full
|
|
|
|
min = 1.0;
|
|
|
|
max = rate/2.;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Zoom out
|
|
|
|
float p1;
|
|
|
|
p1 = (mZoomStart - ypos) / (float)height;
|
|
|
|
c = 1.0-p1;
|
|
|
|
double xmin = c - 1.;
|
|
|
|
double xmax = c + 1.;
|
|
|
|
double lmin = log10(min), lmax = log10(max);
|
|
|
|
double d = lmax-lmin;
|
|
|
|
min = wxMax(1,pow(10, xmin*d+lmin));
|
|
|
|
max = wxMin(rate/2., pow(10, xmax*d+lmin));
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-11-28 01:11:09 +00:00
|
|
|
if (event.ShiftDown() && event.RightUp()) {
|
2014-11-08 14:30:19 +00:00
|
|
|
// Zoom out full
|
|
|
|
min = -1.0;
|
|
|
|
max = 1.0;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
else {
|
2013-11-28 01:11:09 +00:00
|
|
|
// Zoom out
|
|
|
|
if (min <= -1.0 && max >= 1.0) {
|
|
|
|
min = -2.0;
|
|
|
|
max = 2.0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
c = 0.5*(min+max);
|
|
|
|
l = (c - min);
|
2014-11-24 18:03:33 +00:00
|
|
|
// limit to +/- 1 range unless already outside that range...
|
|
|
|
float minRange = (min < -1) ? -2.0 : -1.0;
|
|
|
|
float maxRange = (max > 1) ? 2.0 : 1.0;
|
|
|
|
// and enforce vertical zoom limits.
|
|
|
|
min = wxMin(maxRange - ZOOMLIMIT, wxMax(minRange, c - 2*l));
|
|
|
|
max = wxMax(minRange + ZOOMLIMIT, wxMin(maxRange, c + 2*l));
|
2013-11-28 01:11:09 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Zoom IN
|
|
|
|
float p1;
|
|
|
|
if(spectrum) {
|
|
|
|
c = 0.5*(min+max);
|
|
|
|
// Enforce maximum vertical zoom
|
|
|
|
l = wxMax( minBins * binSize, (c - min));
|
|
|
|
|
|
|
|
p1 = (mZoomStart - ypos) / (float)height;
|
|
|
|
c = (max * (1.0-p1) + min * p1);
|
|
|
|
min = wxMax( 0.0, c - 0.5*l);
|
|
|
|
max = wxMin( rate/2., min + l);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if(spectrumLog) {
|
|
|
|
p1 = (mZoomStart - ypos) / (float)height;
|
|
|
|
c = 1.0-p1;
|
|
|
|
double xmin = c - 0.25;
|
|
|
|
double xmax = c + 0.25;
|
|
|
|
double lmin = log10(min), lmax = log10(max);
|
|
|
|
double d = lmax-lmin;
|
|
|
|
min = wxMax(1,pow(10, xmin*d+lmin));
|
|
|
|
max = wxMin(rate/2., pow(10, xmax*d+lmin));
|
|
|
|
// Enforce vertical zoom limits
|
|
|
|
// done in the linear freq domain for now, but not too far out
|
|
|
|
if(max < min + minBins * binSize)
|
|
|
|
max = min + minBins * binSize;
|
|
|
|
if(max > rate/2.) {
|
|
|
|
max = rate/2.;
|
|
|
|
min = max - minBins * binSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Zoom in centered on cursor
|
|
|
|
if (min < -1.0 || max > 1.0) {
|
|
|
|
min = -1.0;
|
|
|
|
max = 1.0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
c = 0.5*(min+max);
|
|
|
|
// Enforce maximum vertical zoom
|
2013-11-28 01:11:09 +00:00
|
|
|
l = wxMax( ZOOMLIMIT, (c - min));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
p1 = (mZoomStart - ypos) / (float)height;
|
|
|
|
c = (max * (1.0-p1) + min * p1);
|
|
|
|
min = c - 0.5*l;
|
|
|
|
max = c + 0.5*l;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(spectrum) {
|
|
|
|
mTrackArtist->SetSpectrumMaxFreq(max);
|
|
|
|
mTrackArtist->SetSpectrumMinFreq(min);
|
|
|
|
mTrackArtist->InvalidateSpectrumCache(mTracks);
|
|
|
|
}
|
|
|
|
else if(spectrumLog) {
|
|
|
|
mTrackArtist->SetSpectrumLogMaxFreq(max);
|
|
|
|
mTrackArtist->SetSpectrumLogMinFreq(min);
|
|
|
|
mTrackArtist->InvalidateSpectrumCache(mTracks);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
track->SetDisplayBounds(min, max);
|
|
|
|
if (partner)
|
|
|
|
partner->SetDisplayBounds(min, max);
|
|
|
|
}
|
|
|
|
|
|
|
|
mZoomEnd = mZoomStart = 0;
|
|
|
|
UpdateVRuler(mCapturedTrack);
|
|
|
|
Refresh(false);
|
|
|
|
mCapturedTrack = NULL;
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines if we can edit samples in a wave track.
|
|
|
|
/// Also pops up warning messages in certain cases where we can't.
|
|
|
|
/// @return true if we can edit the samples, false otherwise.
|
2013-08-25 21:51:26 +00:00
|
|
|
bool TrackPanel::IsSampleEditingPossible( wxMouseEvent & WXUNUSED(event), Track * t )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
//Exit if we don't have a track
|
|
|
|
if(!t)
|
|
|
|
return false;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Exit if it's not a WaveTrack
|
|
|
|
if(t->GetKind() != Track::Wave)
|
|
|
|
return false;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get out of here if we shouldn't be drawing right now:
|
|
|
|
//If we aren't displaying the waveform, Display a message dialog
|
|
|
|
if(((WaveTrack *)t)->GetDisplay() != WaveTrack::WaveformDisplay)
|
|
|
|
{
|
|
|
|
wxMessageBox(_("To use Draw, choose 'Waveform' in the Track Drop-down Menu."), wxT("Draw Tool"));
|
|
|
|
return false;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get rate in order to calculate the critical zoom threshold
|
|
|
|
//Find out the zoom level
|
|
|
|
double rate = ((WaveTrack *)t)->GetRate();
|
|
|
|
bool showPoints = (mViewInfo->zoom / rate > 3.0);
|
|
|
|
|
|
|
|
//If we aren't zoomed in far enough, show a message dialog.
|
|
|
|
if(!showPoints)
|
|
|
|
{
|
|
|
|
// Release capture so user will be able to click OK on Linux
|
|
|
|
bool hadCapture = HasCapture();
|
|
|
|
if (hadCapture)
|
|
|
|
ReleaseMouse();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxMessageBox(_("To use Draw, zoom in further until you can see the individual samples."), wxT("Draw Tool"));
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Re-aquire capture
|
|
|
|
if (hadCapture)
|
|
|
|
CaptureMouse();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// We're in a track view and zoomed enough to see the samples.
|
|
|
|
/// Someone has just clicked the mouse. What do we do?
|
|
|
|
void TrackPanel::HandleSampleEditingClick( wxMouseEvent & event )
|
|
|
|
{
|
|
|
|
//declare a rectangle to determine clicking position
|
|
|
|
wxRect r;
|
|
|
|
Track *t;
|
|
|
|
|
|
|
|
//Get the track the mouse is over, and save it away for future events
|
|
|
|
mDrawingTrack = NULL;
|
|
|
|
t = FindTrack(event.m_x, event.m_y, false, false, &r);
|
2011-11-03 22:19:50 +00:00
|
|
|
|
|
|
|
if (!t || (t->GetKind() != Track::Wave))
|
2013-02-20 23:42:58 +00:00
|
|
|
return;
|
2011-04-23 18:53:48 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if( !IsSampleEditingPossible( event, t ) )
|
|
|
|
{
|
|
|
|
if( HasCapture() )
|
|
|
|
ReleaseMouse();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \todo Should mCapturedTrack take the place of mDrawingTrack??
|
|
|
|
mDrawingTrack = t;
|
|
|
|
mDrawingTrackTop=r.y;
|
|
|
|
|
|
|
|
//If we are still around, we are drawing in earnest. Set some member data structures up:
|
|
|
|
//First, calculate the starting sample. To get this, we need the time
|
|
|
|
double t0 = PositionToTime(event.m_x, GetLeftOffset());
|
|
|
|
double rate = ((WaveTrack *)mDrawingTrack)->GetRate();
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Default to zero for ALT case, so it doesn't cause a runtime fault on MSVC in
|
|
|
|
// the mDrawingLastDragSampleValue assignment at the bottom of this method.
|
2010-01-23 19:44:49 +00:00
|
|
|
float newLevel = 0.0f; //Declare this for use later
|
|
|
|
|
|
|
|
//convert t0 to samples
|
|
|
|
mDrawingStartSample = (sampleCount) (double)(t0 * rate + 0.5 );
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Now, figure out what the value of that sample is.
|
|
|
|
//First, get the sequence of samples so you can mess with it
|
|
|
|
//Sequence *seq = ((WaveTrack *)mDrawingTrack)->GetSequence();
|
|
|
|
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//Determine how drawing should occur. If alt is down,
|
2010-01-23 19:44:49 +00:00
|
|
|
//do a smoothing, instead of redrawing.
|
2014-06-03 20:30:19 +00:00
|
|
|
if( event.m_altDown )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
//*************************************************
|
|
|
|
//*** ALT-DOWN-CLICK (SAMPLE SMOOTHING) ***
|
|
|
|
//*************************************************
|
|
|
|
//
|
|
|
|
// Smoothing works like this: There is a smoothing kernel radius constant that
|
2014-06-03 20:30:19 +00:00
|
|
|
// determines how wide the averaging window is. Plus, there is a smoothing brush radius,
|
2010-01-23 19:44:49 +00:00
|
|
|
// which determines how many pixels wide around the selected pixel this smoothing is applied.
|
|
|
|
//
|
2014-06-03 20:30:19 +00:00
|
|
|
// Samples will be replaced by a mixture of the original points and the smoothed points,
|
|
|
|
// with a triangular mixing probability whose value at the center point is
|
2010-01-23 19:44:49 +00:00
|
|
|
// SMOOTHING_PROPORTION_MAX and at the far bounds is SMOOTHING_PROPORTION_MIN
|
|
|
|
|
|
|
|
//Get the region of samples around the selected point
|
|
|
|
int sampleRegionSize = 1 + 2 * (SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS);
|
|
|
|
float *sampleRegion = new float[sampleRegionSize];
|
|
|
|
float * newSampleRegion = new float[1 + 2 * SMOOTHING_BRUSH_RADIUS];
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get a sample from the track to do some tricks on.
|
2014-06-03 20:30:19 +00:00
|
|
|
((WaveTrack*)mDrawingTrack)->Get((samplePtr)sampleRegion, floatSample,
|
2010-01-23 19:44:49 +00:00
|
|
|
(int)mDrawingStartSample - SMOOTHING_KERNEL_RADIUS - SMOOTHING_BRUSH_RADIUS,
|
|
|
|
sampleRegionSize);
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
//Go through each point of the smoothing brush and apply a smoothing operation.
|
|
|
|
for(j = -SMOOTHING_BRUSH_RADIUS; j <= SMOOTHING_BRUSH_RADIUS; j++){
|
|
|
|
float sumOfSamples = 0;
|
|
|
|
for (i= -SMOOTHING_KERNEL_RADIUS; i <= SMOOTHING_KERNEL_RADIUS; i++){
|
|
|
|
//Go through each point of the smoothing kernel and find the average
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//The average is a weighted average, scaled by a weighting kernel that is simply triangular
|
|
|
|
// A triangular kernel across N items, with a radius of R ( 2 R + 1 points), if the farthest:
|
|
|
|
// points have a probability of a, the entire triangle has total probability of (R + 1)^2.
|
2014-06-03 20:30:19 +00:00
|
|
|
// For sample number i and middle brush sample M, (R + 1 - abs(M-i))/ ((R+1)^2) gives a
|
2010-01-23 19:44:49 +00:00
|
|
|
// legal distribution whose total probability is 1.
|
|
|
|
//
|
|
|
|
//
|
2014-06-03 20:30:19 +00:00
|
|
|
// weighting factor value
|
2010-01-23 19:44:49 +00:00
|
|
|
sumOfSamples += (SMOOTHING_KERNEL_RADIUS + 1 - abs(i)) * sampleRegion[i + j + SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS];
|
|
|
|
|
|
|
|
}
|
|
|
|
newSampleRegion[j + SMOOTHING_BRUSH_RADIUS] = sumOfSamples/((SMOOTHING_KERNEL_RADIUS + 1) *(SMOOTHING_KERNEL_RADIUS + 1) );
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Now that the new sample levels are determined, go through each and mix it appropriately
|
|
|
|
// with the original point, according to a 2-part linear function whose center has probability
|
|
|
|
// SMOOTHING_PROPORTION_MAX and extends out SMOOTHING_BRUSH_RADIUS, at which the probability is
|
|
|
|
// SMOOTHING_PROPORTION_MIN. _MIN and _MAX specify how much of the smoothed curve make it through.
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
float prob;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for(j=-SMOOTHING_BRUSH_RADIUS; j <= SMOOTHING_BRUSH_RADIUS; j++){
|
|
|
|
|
|
|
|
prob = SMOOTHING_PROPORTION_MAX - (float)abs(j)/SMOOTHING_BRUSH_RADIUS * (SMOOTHING_PROPORTION_MAX - SMOOTHING_PROPORTION_MIN);
|
|
|
|
|
|
|
|
newSampleRegion[j+SMOOTHING_BRUSH_RADIUS] =
|
2014-06-03 20:30:19 +00:00
|
|
|
newSampleRegion[j + SMOOTHING_BRUSH_RADIUS] * prob +
|
2010-01-23 19:44:49 +00:00
|
|
|
sampleRegion[SMOOTHING_BRUSH_RADIUS + SMOOTHING_KERNEL_RADIUS + j] * (1 - prob);
|
|
|
|
}
|
|
|
|
//Set the sample to the point of the mouse event
|
|
|
|
((WaveTrack*)mDrawingTrack)->Set((samplePtr)newSampleRegion, floatSample, mDrawingStartSample - SMOOTHING_BRUSH_RADIUS, 1 + 2 * SMOOTHING_BRUSH_RADIUS);
|
|
|
|
|
|
|
|
//Clean this up right away to avoid a memory leak
|
|
|
|
delete[] sampleRegion;
|
|
|
|
delete[] newSampleRegion;
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
//*************************************************
|
|
|
|
//*** PLAIN DOWN-CLICK (NORMAL DRAWING) ***
|
|
|
|
//*************************************************
|
|
|
|
|
|
|
|
//Otherwise (e.g., the alt button is not down) do normal redrawing, based on the mouse position.
|
|
|
|
// Calculate where the mouse is located vertically (between +/- 1)
|
|
|
|
((WaveTrack*)mDrawingTrack)->Get((samplePtr)&mDrawingStartSampleValue, floatSample,(int) mDrawingStartSample, 1);
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
((WaveTrack *)mDrawingTrack)->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
newLevel = zoomMax -
|
|
|
|
((event.m_y - mDrawingTrackTop)/(float)mDrawingTrack->GetHeight()) *
|
|
|
|
(zoomMax - zoomMin);
|
|
|
|
|
|
|
|
//Take the envelope into account
|
|
|
|
Envelope *env = ((WaveTrack *)mDrawingTrack)->GetEnvelopeAtX(event.GetX());
|
|
|
|
|
|
|
|
if (env)
|
|
|
|
{
|
|
|
|
double envValue = env->GetValue(t0);
|
|
|
|
if (envValue > 0)
|
|
|
|
newLevel /= envValue;
|
|
|
|
else
|
|
|
|
newLevel = 0;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Make sure the new level is between +/-1
|
|
|
|
newLevel = newLevel > 1.0 ? 1.0: newLevel;
|
|
|
|
newLevel = newLevel < -1.0 ? -1.0: newLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Set the sample to the point of the mouse event
|
|
|
|
((WaveTrack*)mDrawingTrack)->Set((samplePtr)&newLevel, floatSample, mDrawingStartSample, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Set the member data structures for drawing
|
|
|
|
mDrawingLastDragSample=mDrawingStartSample;
|
|
|
|
mDrawingLastDragSampleValue = newLevel;
|
|
|
|
|
|
|
|
//Redraw the region of the selected track
|
|
|
|
RefreshTrack(mDrawingTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleSampleEditingDrag( wxMouseEvent & event )
|
|
|
|
{
|
|
|
|
//*************************************************
|
|
|
|
//*** DRAG-DRAWING ***
|
|
|
|
//*************************************************
|
|
|
|
|
|
|
|
//The following will happen on a drag or a down-click.
|
|
|
|
// The point should get re-drawn at the location of the mouse.
|
|
|
|
//Exit if the mDrawingTrack is null.
|
|
|
|
if( mDrawingTrack == NULL)
|
|
|
|
return;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Exit dragging if the alt key is down--Don't allow left-right dragging for smoothing operation
|
|
|
|
if (event.m_altDown)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//Get the rate of the sequence, for use later
|
|
|
|
float rate = ((WaveTrack *)mDrawingTrack)->GetRate();
|
|
|
|
sampleCount s0; //declare this for use below. It designates the sample number which to draw.
|
|
|
|
|
|
|
|
// Figure out what time the click was at
|
|
|
|
double t0 = PositionToTime(event.m_x, GetLeftOffset());
|
|
|
|
float newLevel;
|
2014-06-03 20:30:19 +00:00
|
|
|
//Find the point that we want to redraw at. If the control button is down,
|
|
|
|
//adjust only the originally clicked-on sample
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//*************************************************
|
|
|
|
//*** CTRL-DOWN (Hold Initial Sample Constant ***
|
|
|
|
//*************************************************
|
|
|
|
|
|
|
|
if( event.m_controlDown) {
|
|
|
|
s0 = mDrawingStartSample;
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
//*************************************************
|
|
|
|
//*** Normal CLICK-drag (Normal drawing) ***
|
|
|
|
//*************************************************
|
|
|
|
|
|
|
|
//Otherwise, adjust the sample you are dragging over right now.
|
|
|
|
//convert this to samples
|
|
|
|
s0 = (sampleCount) (double)(t0 * rate + 0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Sequence *seq = ((WaveTrack *)mDrawingTrack)->GetSequence();
|
|
|
|
((WaveTrack*)mDrawingTrack)->Get((samplePtr)&mDrawingStartSampleValue, floatSample, (int)mDrawingStartSample, 1);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Otherwise, do normal redrawing, based on the mouse position.
|
|
|
|
// Calculate where the mouse is located vertically (between +/- 1)
|
|
|
|
|
|
|
|
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
((WaveTrack *)mDrawingTrack)->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
newLevel = zoomMax -
|
|
|
|
((event.m_y - mDrawingTrackTop)/(float)mDrawingTrack->GetHeight()) *
|
|
|
|
(zoomMax - zoomMin);
|
|
|
|
|
|
|
|
//Take the envelope into account
|
|
|
|
Envelope *env = ((WaveTrack *)mDrawingTrack)->GetEnvelopeAtX(event.GetX());
|
|
|
|
if (env)
|
|
|
|
{
|
|
|
|
double envValue = env->GetValue(t0);
|
|
|
|
if (envValue > 0)
|
|
|
|
newLevel /= envValue;
|
|
|
|
else
|
|
|
|
newLevel = 0;
|
|
|
|
|
|
|
|
//Make sure the new level is between +/-1
|
|
|
|
newLevel = newLevel > 1.0 ? 1.0: newLevel;
|
|
|
|
newLevel = newLevel < -1.0 ? -1.0: newLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Now, redraw all samples between current and last redrawn sample
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
float tmpvalue;
|
|
|
|
//Handle cases of 0 or 1 special, to improve speed
|
|
|
|
//JKC I don't think this makes any noticeable difference to speed
|
2014-06-03 20:30:19 +00:00
|
|
|
// whatsoever! The real reason for the special case is probably to
|
2010-01-23 19:44:49 +00:00
|
|
|
// avoid division by zero....
|
|
|
|
#define LLABS(n) ((n) < 0 ? -(n) : (n))
|
|
|
|
if(LLABS(s0 - mDrawingLastDragSample) <= 1){
|
2013-10-29 21:49:45 +00:00
|
|
|
((WaveTrack*)mDrawingTrack)->Set((samplePtr)&newLevel, floatSample, s0, 1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
//Go from the smaller to larger sample.
|
2010-01-23 19:44:49 +00:00
|
|
|
int start = wxMin( s0, mDrawingLastDragSample) +1;
|
|
|
|
int end = wxMax( s0, mDrawingLastDragSample);
|
|
|
|
for(sampleCount i= start; i<= end; i++) {
|
|
|
|
//This interpolates each sample linearly:
|
2014-06-03 20:30:19 +00:00
|
|
|
tmpvalue=mDrawingLastDragSampleValue + (newLevel - mDrawingLastDragSampleValue) *
|
2010-01-23 19:44:49 +00:00
|
|
|
(float)(i-mDrawingLastDragSample)/(s0-mDrawingLastDragSample );
|
|
|
|
((WaveTrack*)mDrawingTrack)->Set((samplePtr)&tmpvalue, floatSample, i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Update the member data structures.
|
|
|
|
mDrawingLastDragSample=s0;
|
|
|
|
mDrawingLastDragSampleValue = newLevel;
|
|
|
|
|
|
|
|
//Redraw the region of the selected track
|
|
|
|
RefreshTrack(mDrawingTrack);
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::HandleSampleEditingButtonUp( wxMouseEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
//*************************************************
|
|
|
|
//*** UP-CLICK (Finish drawing) ***
|
|
|
|
//*************************************************
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//On up-click, send the state to the undo stack
|
|
|
|
mDrawingTrack=NULL; //Set this to NULL so it will catch improper drag events.
|
|
|
|
MakeParentPushState(_("Moved Sample"),
|
|
|
|
_("Sample Edit"),
|
2015-04-07 11:20:50 +00:00
|
|
|
PUSH_CONSOLIDATE|PUSH_AUTOSAVE);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// This handles adjusting individual samples by hand using the draw tool(s)
|
|
|
|
///
|
|
|
|
/// There are several member data structure for handling drawing:
|
2014-06-03 20:30:19 +00:00
|
|
|
/// - mDrawingTrack: keeps track of which track you clicked down on, so drawing doesn't
|
2010-01-23 19:44:49 +00:00
|
|
|
/// jump to a new track
|
|
|
|
/// - mDrawingTrackTop: The top position of the drawing track--makes drawing easier.
|
|
|
|
/// - mDrawingStartSample: The sample you clicked down on, so that you can hold it steady
|
|
|
|
/// - mDrawingStartSampleValue: The original value of the initial sample
|
|
|
|
/// - mDrawingLastDragSample: When drag-drawing, this keeps track of the last sample you dragged over,
|
|
|
|
/// so it can smoothly redraw samples that got skipped over
|
2014-06-03 20:30:19 +00:00
|
|
|
/// - mDrawingLastDragSampleValue: The value of the last
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::HandleSampleEditing(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.LeftDown() ) {
|
|
|
|
HandleSampleEditingClick( event);
|
|
|
|
} else if (mDrawingTrack && event.Dragging()) {
|
|
|
|
HandleSampleEditingDrag( event );
|
|
|
|
} else if(mDrawingTrack && event.ButtonUp()) {
|
|
|
|
HandleSampleEditingButtonUp( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is for when a given track gets the x.
|
|
|
|
void TrackPanel::HandleClosing(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
AudacityProject *p = GetProject(); //lda
|
|
|
|
|
|
|
|
Track *t = mCapturedTrack;
|
|
|
|
wxRect r = mCapturedRect;
|
|
|
|
|
|
|
|
wxRect closeRect;
|
|
|
|
mTrackInfo.GetCloseBoxRect(r, closeRect);
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
|
|
|
|
if (event.Dragging())
|
|
|
|
mTrackInfo.DrawCloseBox(&dc, r, closeRect.Contains(event.m_x, event.m_y));
|
|
|
|
else if (event.LeftUp()) {
|
|
|
|
mTrackInfo.DrawCloseBox(&dc, r, false);
|
|
|
|
if (closeRect.Contains(event.m_x, event.m_y)) {
|
|
|
|
if (!gAudioIO->IsStreamActive(p->GetAudioIOToken()))
|
|
|
|
RemoveTrack(t);
|
|
|
|
}
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
}
|
2012-08-16 23:41:09 +00:00
|
|
|
|
|
|
|
this->UpdateViewIfNoTracks();
|
|
|
|
this->Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::UpdateViewIfNoTracks()
|
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
if (mTracks->IsEmpty())
|
2012-08-16 23:41:09 +00:00
|
|
|
{
|
|
|
|
// BG: There are no more tracks on screen
|
2010-01-23 19:44:49 +00:00
|
|
|
//BG: Set zoom to normal
|
|
|
|
mViewInfo->zoom = 44100.0 / 512.0;
|
|
|
|
|
|
|
|
//STM: Set selection to 0,0
|
2014-10-05 17:10:09 +00:00
|
|
|
//PRL: and default the rest of the selection information
|
|
|
|
mViewInfo->selectedRegion = SelectedRegion();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mListener->TP_RedrawScrollbars();
|
|
|
|
mListener->TP_DisplayStatusMessage(wxT("")); //STM: Clear message if all tracks are removed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes the specified track. Called from HandleClosing.
|
|
|
|
void TrackPanel::RemoveTrack(Track * toRemove)
|
|
|
|
{
|
|
|
|
// If it was focused, reassign focus to the next or, if
|
|
|
|
// unavailable, the previous track.
|
|
|
|
if (GetFocusedTrack() == toRemove) {
|
|
|
|
Track *t = mTracks->GetNext(toRemove, true);
|
|
|
|
if (t == NULL) {
|
|
|
|
t = mTracks->GetPrev( toRemove, true );
|
|
|
|
}
|
|
|
|
SetFocusedTrack(t); // It's okay if this is NULL
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString name = toRemove->GetName();
|
|
|
|
Track *partner = toRemove->GetLink();
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
if (toRemove->GetKind() == Track::Wave)
|
|
|
|
{
|
|
|
|
// Update mixer board displayed tracks.
|
2014-06-03 20:30:19 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
2010-07-21 04:53:38 +00:00
|
|
|
if (pMixerBoard)
|
|
|
|
pMixerBoard->RemoveTrackCluster((WaveTrack*)toRemove); // Will remove partner shown in same cluster.
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mTracks->Remove(toRemove, true);
|
|
|
|
if (partner) {
|
|
|
|
mTracks->Remove(partner, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTracks->IsEmpty()) {
|
|
|
|
SetFocusedTrack( NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
MakeParentPushState(
|
|
|
|
wxString::Format(_("Removed track '%s.'"),
|
|
|
|
name.c_str()),
|
|
|
|
_("Track Remove"));
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
MakeParentResize();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandlePopping(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
Track *t = mCapturedTrack;
|
|
|
|
wxRect r = mCapturedRect;
|
|
|
|
|
|
|
|
if( t==NULL ){
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxRect titleRect;
|
|
|
|
mTrackInfo.GetTitleBarRect(r, titleRect);
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
|
|
|
|
if (event.Dragging()) {
|
|
|
|
mTrackInfo.DrawTitleBar(&dc, r, t, titleRect.Contains(event.m_x, event.m_y));
|
|
|
|
}
|
|
|
|
else if (event.LeftUp()) {
|
|
|
|
if (titleRect.Contains(event.m_x, event.m_y))
|
|
|
|
{
|
|
|
|
OnTrackMenu(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
|
|
|
|
mTrackInfo.DrawTitleBar(&dc, r, t, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handle when the mute or solo button is pressed for some track.
|
|
|
|
void TrackPanel::HandleMutingSoloing(wxMouseEvent & event, bool solo)
|
|
|
|
{
|
|
|
|
Track *t = mCapturedTrack;
|
|
|
|
wxRect r = mCapturedRect;
|
|
|
|
|
|
|
|
if( t==NULL ){
|
|
|
|
wxASSERT(false);// Soloing or muting but no captured track!
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxRect buttonRect;
|
|
|
|
mTrackInfo.GetMuteSoloRect(r, buttonRect, solo, HasSoloButton());
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
|
|
|
|
if (event.Dragging()){
|
|
|
|
mTrackInfo.DrawMuteSolo(&dc, r, t, buttonRect.Contains(event.m_x, event.m_y),
|
|
|
|
solo, HasSoloButton());
|
|
|
|
}
|
|
|
|
else if (event.LeftUp() )
|
2013-10-29 21:49:45 +00:00
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
if (buttonRect.Contains(event.m_x, event.m_y))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// For either, MakeParentPushState to make the track state dirty.
|
|
|
|
if(solo)
|
|
|
|
OnTrackSolo(event.ShiftDown(),t);
|
|
|
|
else
|
|
|
|
OnTrackMute(event.ShiftDown(),t);
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
// mTrackInfo.DrawMuteSolo(&dc, r, t, false, solo);
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleMinimizing(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
Track *t = mCapturedTrack;
|
|
|
|
wxRect r = mCapturedRect;
|
|
|
|
|
|
|
|
if (t == NULL) {
|
|
|
|
SetCapturedTrack(NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxRect buttonRect;
|
2010-09-16 23:08:33 +00:00
|
|
|
mTrackInfo.GetMinimizeRect(r, buttonRect);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
|
|
|
|
if (event.Dragging()) {
|
2010-08-25 22:34:17 +00:00
|
|
|
mTrackInfo.DrawMinimize(&dc, r, t, buttonRect.Contains(event.m_x, event.m_y));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else if (event.LeftUp()) {
|
|
|
|
if (buttonRect.Contains(event.m_x, event.m_y)) {
|
|
|
|
t->SetMinimized(!t->GetMinimized());
|
|
|
|
if (mTracks->GetLink(t))
|
|
|
|
mTracks->GetLink(t)->SetMinimized(t->GetMinimized());
|
|
|
|
MakeParentRedrawScrollbars();
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SetCapturedTrack(NULL);
|
|
|
|
|
2010-08-25 22:34:17 +00:00
|
|
|
mTrackInfo.DrawMinimize(&dc, r, t, false);
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
GetActiveProject()->RedrawProject();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan)
|
|
|
|
{
|
|
|
|
LWSlider *slider;
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
bool panZero = false;
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (pan)
|
2010-04-21 05:03:24 +00:00
|
|
|
slider = mTrackInfo.PanSlider(mCapturedTrack->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
2010-04-21 05:03:24 +00:00
|
|
|
slider = mTrackInfo.GainSlider(mCapturedTrack->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
slider->OnMouseEvent(event);
|
|
|
|
|
|
|
|
//If we have a double-click, do this...
|
|
|
|
if (event.LeftDClick())
|
|
|
|
mMouseCapture = IsUncaptured;
|
|
|
|
|
|
|
|
float newValue = slider->Get();
|
2010-07-21 04:53:38 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too.
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2010-09-18 21:02:36 +00:00
|
|
|
if (mCapturedTrack->GetKind() == Track::Wave) {
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-09-18 21:02:36 +00:00
|
|
|
WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (pan) {
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
panZero = ((WaveTrack *)mCapturedTrack)->SetPan(newValue);
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
((WaveTrack *)mCapturedTrack)->SetPan(newValue);
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
if (link)
|
|
|
|
link->SetPan(newValue);
|
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(panZero) MakeParentRedrawScrollbars();
|
|
|
|
#endif
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pMixerBoard)
|
2010-07-21 04:53:38 +00:00
|
|
|
pMixerBoard->UpdatePan((WaveTrack*)mCapturedTrack);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
((WaveTrack *)mCapturedTrack)->SetGain(newValue);
|
|
|
|
if (link)
|
|
|
|
link->SetGain(newValue);
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pMixerBoard)
|
2010-07-21 04:53:38 +00:00
|
|
|
pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2011-10-19 23:06:53 +00:00
|
|
|
} else { // Note: funny indentation to match "if" about 20 lines back
|
2010-09-18 21:02:36 +00:00
|
|
|
if (!pan) {
|
|
|
|
((NoteTrack *) mCapturedTrack)->SetGain(newValue);
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIXER_BOARD
|
2010-09-18 21:02:36 +00:00
|
|
|
if (pMixerBoard)
|
|
|
|
// probably should modify UpdateGain to take a track that is
|
|
|
|
// either a WaveTrack or a NoteTrack.
|
|
|
|
pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack);
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-09-18 21:02:36 +00:00
|
|
|
}
|
2010-10-07 21:36:39 +00:00
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
VisibleTrackIterator iter(GetProject());
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next())
|
|
|
|
{
|
|
|
|
RefreshTrack(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.ButtonUp()) {
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2010-10-28 17:34:35 +00:00
|
|
|
if (mCapturedTrack->GetKind() == Track::Wave) {
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(pan ? _("Moved pan slider") : _("Moved gain slider"),
|
|
|
|
pan ? _("Pan") : _("Gain"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2010-10-28 17:34:35 +00:00
|
|
|
} else {
|
|
|
|
MakeParentPushState(_("Moved velocity slider"), _("Velocity"), true);
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The tracks positions within the list have changed, so update the vertical
|
|
|
|
// ruler size for the track that triggered the event.
|
|
|
|
void TrackPanel::OnTrackListResized(wxCommandEvent & e)
|
|
|
|
{
|
|
|
|
Track *t = (Track *) e.GetClientData();
|
|
|
|
|
|
|
|
UpdateVRuler(t);
|
|
|
|
|
|
|
|
e.Skip();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tracks have been added or removed from the list. Handle adds as if
|
|
|
|
// a resize has taken place.
|
|
|
|
void TrackPanel::OnTrackListUpdated(wxCommandEvent & e)
|
|
|
|
{
|
|
|
|
// Tracks may have been deleted, so check to see if the focused track was on of them.
|
|
|
|
if (!mTracks->Contains(GetFocusedTrack())) {
|
|
|
|
SetFocusedTrack(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.GetClientData()) {
|
|
|
|
OnTrackListResized(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
e.Skip();
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
OnTrackMenu();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This handles when the user clicks on the "Label" area
|
|
|
|
/// of a track, ie the part with all the buttons and the drop
|
|
|
|
/// down menu, etc.
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
// That is, TrackInfo and vertical ruler rect.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::HandleLabelClick(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
// AS: If not a click, ignore the mouse event.
|
|
|
|
if (!event.ButtonDown() && !event.ButtonDClick()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MIDI tracks use the right mouse button, but other tracks get confused
|
|
|
|
// if they see anything other than a left click.
|
|
|
|
bool isleft = event.Button(wxMOUSE_BTN_LEFT);
|
|
|
|
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
bool unsafe = (p->GetAudioIOToken()>0 &&
|
|
|
|
gAudioIO->IsStreamActive(p->GetAudioIOToken()));
|
|
|
|
|
|
|
|
wxRect r;
|
|
|
|
|
|
|
|
Track *t = FindTrack(event.m_x, event.m_y, true, true, &r);
|
|
|
|
|
|
|
|
// AS: If the user clicked outside all tracks, make nothing
|
|
|
|
// selected.
|
|
|
|
if (!t) {
|
|
|
|
SelectNone();
|
|
|
|
Refresh(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// LL: Check close box
|
|
|
|
if (isleft && CloseFunc(t, r, event.m_x, event.m_y))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// LL: Check title bar for popup
|
|
|
|
if (isleft && PopupFunc(t, r, event.m_x, event.m_y))
|
|
|
|
return;
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// VJ: Check sync-lock icon and the blank area to the left of the minimize button.
|
|
|
|
// Have to do it here, because if track is shrunk such that these areas occlude controls,
|
|
|
|
// e.g., mute/solo, don't want the "Funcs" below to set up handling.
|
2010-09-16 23:08:33 +00:00
|
|
|
// Only result of doing so is to select the track. Don't care whether isleft.
|
|
|
|
bool bTrackSelClick = this->TrackSelFunc(t, r, event.m_x, event.m_y);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!bTrackSelClick)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-12 05:11:43 +00:00
|
|
|
// MM: Check minimize buttons on WaveTracks. Must be before
|
|
|
|
// solo/mute buttons, sliders etc.
|
|
|
|
if (isleft && MinimizeFunc(t, r, event.m_x, event.m_y))
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
if (isleft && t->GetKind() == Track::Wave)
|
|
|
|
{
|
|
|
|
// DM: Check Mute and Solo buttons on WaveTracks:
|
|
|
|
if (MuteSoloFunc(t, r, event.m_x, event.m_y, false) ||
|
|
|
|
MuteSoloFunc(t, r, event.m_x, event.m_y, true))
|
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
if (GainFunc(t, r, event, event.m_x, event.m_y))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (PanFunc(t, r, event, event.m_x, event.m_y))
|
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef USE_MIDI
|
2010-09-12 05:11:43 +00:00
|
|
|
// DM: If it's a NoteTrack, it has special controls
|
|
|
|
else if (t->GetKind() == Track::Note)
|
|
|
|
{
|
|
|
|
wxRect midiRect;
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2010-09-18 21:02:36 +00:00
|
|
|
// this is an awful hack: make a new rectangle at an offset because
|
|
|
|
// MuteSoloFunc thinks buttons are located below some text, e.g.
|
|
|
|
// "Mono, 44100Hz 32-bit float", but this is not true for a Note track
|
|
|
|
wxRect muteSoloRect(r);
|
|
|
|
muteSoloRect.y -= 34; // subtract the height of wave track text
|
|
|
|
if (MuteSoloFunc(t, muteSoloRect, event.m_x, event.m_y, false) ||
|
|
|
|
MuteSoloFunc(t, muteSoloRect, event.m_x, event.m_y, true))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// this is a similar hack: GainFunc expects a Wave track slider, so it's
|
|
|
|
// looking in the wrong place. We pass it a bogus rectangle created when
|
|
|
|
// the slider was placed to "fake" GainFunc into finding the slider in
|
|
|
|
// its actual location.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (GainFunc(t, ((NoteTrack *) t)->GetGainPlacementRect(),
|
2010-09-18 21:02:36 +00:00
|
|
|
event, event.m_x, event.m_y))
|
|
|
|
return;
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-09-12 05:11:43 +00:00
|
|
|
mTrackInfo.GetTrackControlsRect(r, midiRect);
|
2010-09-18 21:02:36 +00:00
|
|
|
if (midiRect.Contains(event.m_x, event.m_y) &&
|
|
|
|
((NoteTrack *) t)->LabelClick(midiRect, event.m_x, event.m_y,
|
|
|
|
event.Button(wxMOUSE_BTN_RIGHT))) {
|
2010-09-12 05:11:43 +00:00
|
|
|
Refresh(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif // USE_MIDI
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
if (!isleft) {
|
|
|
|
return;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DM: If they weren't clicking on a particular part of a track label,
|
|
|
|
// deselect other tracks and select this one.
|
|
|
|
|
|
|
|
// JH: also, capture the current track for rearranging, so the user
|
|
|
|
// can drag the track up or down to swap it with others
|
|
|
|
if (!unsafe) {
|
|
|
|
SetCapturedTrack( t, IsRearranging );
|
|
|
|
TrackPanel::CalculateRearrangingThresholds(event);
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// AS: If the shift button is being held down, invert
|
2010-01-23 19:44:49 +00:00
|
|
|
// the selection on this track.
|
|
|
|
if (event.ShiftDown()) {
|
|
|
|
mTracks->Select(t, !t->GetSelected());
|
2010-02-12 16:05:02 +00:00
|
|
|
Refresh(false);
|
2010-07-21 04:53:38 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard && (t->GetKind() == Track::Wave))
|
|
|
|
pMixerBoard->RefreshTrackCluster((WaveTrack*)t);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SelectNone();
|
|
|
|
mTracks->Select(t);
|
|
|
|
SetFocusedTrack(t);
|
|
|
|
SelectTrackLength(t);
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
this->Refresh(false);
|
2010-07-21 04:53:38 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard)
|
|
|
|
pMixerBoard->RefreshTrackClusters();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (!unsafe)
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The user is dragging one of the tracks: change the track order
|
|
|
|
/// accordingly
|
|
|
|
void TrackPanel::HandleRearrange(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
// are we finishing the drag?
|
|
|
|
if (event.LeftUp()) {
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
SetCursor(*mArrowCursor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too.
|
2010-01-23 19:44:49 +00:00
|
|
|
wxString dir;
|
|
|
|
if (event.m_y < mMoveUpThreshold || event.m_y < 0) {
|
|
|
|
mTracks->MoveUp(mCapturedTrack);
|
|
|
|
dir = _("up");
|
2011-10-19 23:06:53 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|
|
if (pMixerBoard && (mCapturedTrack->GetKind() == Track::Wave ||
|
|
|
|
mCapturedTrack->GetKind() == Track::Note))
|
|
|
|
pMixerBoard->MoveTrackCluster(mCapturedTrack, true /* up */);
|
|
|
|
#else
|
2010-07-21 04:53:38 +00:00
|
|
|
if (pMixerBoard && (mCapturedTrack->GetKind() == Track::Wave))
|
2011-10-19 23:06:53 +00:00
|
|
|
pMixerBoard->MoveTrackCluster((WaveTrack*)mCapturedTrack, true /* up */);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else if (event.m_y > mMoveDownThreshold || event.m_y > GetRect().GetHeight()) {
|
|
|
|
mTracks->MoveDown(mCapturedTrack);
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: a direction as in up or down.*/
|
2010-01-23 19:44:49 +00:00
|
|
|
dir = _("down");
|
2011-10-19 23:06:53 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|
|
if (pMixerBoard && (mCapturedTrack->GetKind() == Track::Wave ||
|
|
|
|
mCapturedTrack->GetKind() == Track::Note))
|
|
|
|
pMixerBoard->MoveTrackCluster(mCapturedTrack, false /* down */);
|
|
|
|
#else
|
2010-07-21 04:53:38 +00:00
|
|
|
if (pMixerBoard && (mCapturedTrack->GetKind() == Track::Wave))
|
2011-10-19 23:06:53 +00:00
|
|
|
pMixerBoard->MoveTrackCluster((WaveTrack*)mCapturedTrack, false /* down */);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MakeParentPushState(wxString::Format(_("Moved '%s' %s"),
|
|
|
|
mCapturedTrack->GetName().c_str(),
|
|
|
|
dir.c_str()),
|
|
|
|
_("Move Track"));
|
|
|
|
|
|
|
|
// JH: if we moved up or down, recalculate the thresholds and make sure the
|
|
|
|
// track is fully on-screen.
|
|
|
|
TrackPanel::CalculateRearrangingThresholds(event);
|
|
|
|
EnsureVisible(mCapturedTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Figure out how far the user must drag the mouse up or down
|
|
|
|
/// before the track will swap with the one above or below
|
|
|
|
void TrackPanel::CalculateRearrangingThresholds(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
wxASSERT(mCapturedTrack);
|
|
|
|
|
|
|
|
// JH: this will probably need to be tweaked a bit, I'm just
|
|
|
|
// not sure what formula will have the best feel for the
|
|
|
|
// user.
|
|
|
|
if (mTracks->CanMoveUp(mCapturedTrack))
|
|
|
|
mMoveUpThreshold =
|
|
|
|
event.m_y - mTracks->GetGroupHeight( mTracks->GetPrev(mCapturedTrack,true) );
|
|
|
|
else
|
|
|
|
mMoveUpThreshold = INT_MIN;
|
|
|
|
|
|
|
|
if (mTracks->CanMoveDown(mCapturedTrack))
|
|
|
|
mMoveDownThreshold =
|
|
|
|
event.m_y + mTracks->GetGroupHeight( mTracks->GetNext(mCapturedTrack,true) );
|
|
|
|
else
|
|
|
|
mMoveDownThreshold = INT_MAX;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::GainFunc(Track * t, wxRect r, wxMouseEvent &event,
|
|
|
|
int x, int y)
|
|
|
|
{
|
|
|
|
wxRect sliderRect;
|
|
|
|
mTrackInfo.GetGainRect(r, sliderRect);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!sliderRect.Contains(x, y))
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
SetCapturedTrack( t, IsGainSliding);
|
|
|
|
mCapturedRect = r;
|
|
|
|
HandleSliders(event, false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::PanFunc(Track * t, wxRect r, wxMouseEvent &event,
|
|
|
|
int x, int y)
|
|
|
|
{
|
|
|
|
wxRect sliderRect;
|
|
|
|
mTrackInfo.GetPanRect(r, sliderRect);
|
|
|
|
if (!sliderRect.Contains(x, y))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
SetCapturedTrack( t, IsPanSliding);
|
|
|
|
mCapturedRect = r;
|
|
|
|
HandleSliders(event, true);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// Mute or solo the given track (t). If solo is true, we're
|
|
|
|
/// soloing, otherwise we're muting. Basically, check and see
|
2010-01-23 19:44:49 +00:00
|
|
|
/// whether x and y fall within the area of the appropriate button.
|
|
|
|
bool TrackPanel::MuteSoloFunc(Track * t, wxRect r, int x, int y,
|
|
|
|
bool solo)
|
|
|
|
{
|
|
|
|
wxRect buttonRect;
|
|
|
|
mTrackInfo.GetMuteSoloRect(r, buttonRect, solo, HasSoloButton());
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!buttonRect.Contains(x, y))
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
SetCapturedTrack( t, solo ? IsSoloing : IsMuting);
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
mTrackInfo.DrawMuteSolo(&dc, r, t, true, solo, HasSoloButton());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
bool TrackPanel::TrackSelFunc(Track * WXUNUSED(t), wxRect r, int x, int y)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
// First check the blank space to left of minimize button.
|
|
|
|
wxRect selRect;
|
|
|
|
mTrackInfo.GetMinimizeRect(r, selRect); // for y and height
|
|
|
|
selRect.x = r.x;
|
|
|
|
selRect.width = 16; // (kTrackInfoBtnSize)
|
|
|
|
selRect.height++;
|
|
|
|
if (selRect.Contains(x, y))
|
|
|
|
return true;
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
// Try the sync-lock rect.
|
|
|
|
mTrackInfo.GetSyncLockIconRect(r, selRect);
|
|
|
|
selRect.height++;
|
|
|
|
return selRect.Contains(x, y);
|
2010-09-12 05:11:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::MinimizeFunc(Track * t, wxRect r, int x, int y)
|
|
|
|
{
|
|
|
|
wxRect buttonRect;
|
2010-09-16 23:08:33 +00:00
|
|
|
mTrackInfo.GetMinimizeRect(r, buttonRect);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!buttonRect.Contains(x, y))
|
2010-09-12 05:11:43 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
SetCapturedTrack(t, IsMinimizing);
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
mTrackInfo.DrawMinimize(&dc, r, t, true);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::CloseFunc(Track * t, wxRect r, int x, int y)
|
|
|
|
{
|
|
|
|
wxRect closeRect;
|
|
|
|
mTrackInfo.GetCloseBoxRect(r, closeRect);
|
|
|
|
|
|
|
|
if (!closeRect.Contains(x, y))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
SetCapturedTrack( t, IsClosing );
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
mTrackInfo.DrawCloseBox(&dc, r, true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::PopupFunc(Track * t, wxRect r, int x, int y)
|
|
|
|
{
|
|
|
|
wxRect titleRect;
|
|
|
|
mTrackInfo.GetTitleBarRect(r, titleRect);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!titleRect.Contains(x, y))
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
wxClientDC dc(this);
|
|
|
|
SetCapturedTrack( t, IsPopping );
|
|
|
|
mCapturedRect = r;
|
|
|
|
|
|
|
|
mTrackInfo.DrawTitleBar(&dc, r, t, true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// ButtonDown means they just clicked and haven't released yet.
|
|
|
|
/// We use this opportunity to save which track they clicked on,
|
|
|
|
/// and the initial height of the track, so as they drag we can
|
|
|
|
/// update the track size.
|
|
|
|
void TrackPanel::HandleResizeClick( wxMouseEvent & event )
|
|
|
|
{
|
|
|
|
wxRect r;
|
|
|
|
wxRect rLabel;
|
|
|
|
|
|
|
|
// DM: Figure out what track is about to be resized
|
|
|
|
Track *t = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
Track *label = FindTrack(event.m_x, event.m_y, true, true, &rLabel);
|
|
|
|
|
|
|
|
// If the click is at the bottom of a non-linked track label, we
|
|
|
|
// treat it as a normal resize. If the label is of a linked track,
|
|
|
|
// we ignore the click.
|
|
|
|
|
|
|
|
if (label && !label->GetLinked()) {
|
|
|
|
t = label;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!t) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mMouseClickX = event.m_x;
|
|
|
|
mMouseClickY = event.m_y;
|
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
2015-04-12 03:45:46 +00:00
|
|
|
// To do: escape key
|
2013-05-30 23:14:25 +00:00
|
|
|
if(MONO_WAVE_PAN(t)){
|
|
|
|
//STM: Determine whether we should rescale one or two tracks
|
|
|
|
if (t->GetVirtualStereo()) {
|
|
|
|
// mCapturedTrack is the lower track
|
|
|
|
mInitialTrackHeight = t->GetHeight(true);
|
|
|
|
mInitialUpperTrackHeight = t->GetHeight();
|
|
|
|
SetCapturedTrack(t, IsResizingBelowLinkedTracks);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// mCapturedTrack is the upper track
|
|
|
|
mInitialTrackHeight = t->GetHeight(true);
|
|
|
|
mInitialUpperTrackHeight = t->GetHeight();
|
|
|
|
SetCapturedTrack(t, IsResizingBetweenLinkedTracks);
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
Track *prev = mTracks->GetPrev(t);
|
|
|
|
Track *next = mTracks->GetNext(t);
|
|
|
|
|
|
|
|
//STM: Determine whether we should rescale one or two tracks
|
|
|
|
if (prev && prev->GetLink() == t) {
|
|
|
|
// mCapturedTrack is the lower track
|
|
|
|
mInitialTrackHeight = t->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialMinimized = t->GetMinimized();
|
2013-05-30 23:14:25 +00:00
|
|
|
mInitialUpperTrackHeight = prev->GetHeight();
|
|
|
|
SetCapturedTrack(t, IsResizingBelowLinkedTracks);
|
|
|
|
}
|
|
|
|
else if (next && t->GetLink() == next) {
|
|
|
|
// mCapturedTrack is the upper track
|
|
|
|
mInitialTrackHeight = next->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialMinimized = next->GetMinimized();
|
2013-05-30 23:14:25 +00:00
|
|
|
mInitialUpperTrackHeight = t->GetHeight();
|
|
|
|
SetCapturedTrack(t, IsResizingBetweenLinkedTracks);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// DM: Save the initial mouse location and the initial height
|
|
|
|
mInitialTrackHeight = t->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialMinimized = t->GetMinimized();
|
2013-05-30 23:14:25 +00:00
|
|
|
SetCapturedTrack(t, IsResizing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else // EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
Track *prev = mTracks->GetPrev(t);
|
|
|
|
Track *next = mTracks->GetNext(t);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//STM: Determine whether we should rescale one or two tracks
|
|
|
|
if (prev && prev->GetLink() == t) {
|
|
|
|
// mCapturedTrack is the lower track
|
|
|
|
mInitialTrackHeight = t->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialActualHeight = t->GetActualHeight();
|
|
|
|
mInitialMinimized = t->GetMinimized();
|
2010-01-23 19:44:49 +00:00
|
|
|
mInitialUpperTrackHeight = prev->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialUpperActualHeight = prev->GetActualHeight();
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack(t, IsResizingBelowLinkedTracks);
|
|
|
|
}
|
|
|
|
else if (next && t->GetLink() == next) {
|
|
|
|
// mCapturedTrack is the upper track
|
|
|
|
mInitialTrackHeight = next->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialActualHeight = next->GetActualHeight();
|
|
|
|
mInitialMinimized = next->GetMinimized();
|
2010-01-23 19:44:49 +00:00
|
|
|
mInitialUpperTrackHeight = t->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialUpperActualHeight = t->GetActualHeight();
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack(t, IsResizingBetweenLinkedTracks);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// DM: Save the initial mouse location and the initial height
|
|
|
|
mInitialTrackHeight = t->GetHeight();
|
2015-04-12 03:45:46 +00:00
|
|
|
mInitialActualHeight = t->GetActualHeight();
|
|
|
|
mInitialMinimized = t->GetMinimized();
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack(t, IsResizing);
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif // EXPERIMENTAL_OUTPUT_DISPLAY
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// This happens when the button is released from a drag.
|
|
|
|
/// Since we actually took care of resizing the track when
|
|
|
|
/// we got drag events, all we have to do here is clean up.
|
|
|
|
/// We also modify the undo state (the action doesn't become
|
|
|
|
/// undo-able, but it gets merged with the previous undo-able
|
|
|
|
/// event).
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::HandleResizeButtonUp(wxMouseEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
MakeParentRedrawScrollbars();
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Resize dragging means that the mouse button IS down and has moved
|
|
|
|
/// from its initial location. By the time we get here, we
|
|
|
|
/// have already received a ButtonDown() event and saved the
|
|
|
|
/// track being resized in mCapturedTrack.
|
|
|
|
void TrackPanel::HandleResizeDrag(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
int delta = (event.m_y - mMouseClickY);
|
|
|
|
|
|
|
|
// On first drag, jump out of minimized mode. Initial height
|
|
|
|
// will be height of minimized track.
|
|
|
|
//
|
|
|
|
// This used to be in HandleResizeClick(), but simply clicking
|
|
|
|
// on a resize border would switch the minimized state.
|
|
|
|
if (mCapturedTrack->GetMinimized()) {
|
|
|
|
Track *link = mCapturedTrack->GetLink();
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(mCapturedTrack->GetHeight());
|
|
|
|
mCapturedTrack->SetMinimized(false);
|
|
|
|
|
|
|
|
if (link) {
|
|
|
|
link->SetHeight(link->GetHeight());
|
|
|
|
link->SetMinimized(false);
|
2014-06-03 20:30:19 +00:00
|
|
|
// Initial values must be reset since they weren't based on the
|
2011-11-25 21:26:01 +00:00
|
|
|
// minimized heights.
|
2010-01-23 19:44:49 +00:00
|
|
|
mInitialUpperTrackHeight = link->GetHeight();
|
|
|
|
mInitialTrackHeight = mCapturedTrack->GetHeight();
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
else if(MONO_WAVE_PAN(mCapturedTrack)){
|
|
|
|
mCapturedTrack->SetMinimized(false);
|
|
|
|
mInitialUpperTrackHeight = mCapturedTrack->GetHeight();
|
|
|
|
mInitialTrackHeight = mCapturedTrack->GetHeight(true);
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//STM: We may be dragging one or two (stereo) tracks.
|
2010-01-23 19:44:49 +00:00
|
|
|
// If two, resize proportionally if we are dragging the lower track, and
|
|
|
|
// adjust compensatively if we are dragging the upper track.
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
switch( mMouseCapture )
|
|
|
|
{
|
|
|
|
case IsResizingBelowLinkedTracks:
|
|
|
|
{
|
|
|
|
if(MONO_WAVE_PAN(mCapturedTrack)){
|
|
|
|
double proportion = static_cast < double >(mInitialTrackHeight)
|
|
|
|
/ (mInitialTrackHeight + mInitialUpperTrackHeight);
|
|
|
|
|
|
|
|
int newTrackHeight = static_cast < int >
|
|
|
|
(mInitialTrackHeight + delta * proportion);
|
|
|
|
|
|
|
|
int newUpperTrackHeight = static_cast < int >
|
|
|
|
(mInitialUpperTrackHeight + delta * (1.0 - proportion));
|
|
|
|
|
|
|
|
//make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
if (newUpperTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newUpperTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight,true);
|
|
|
|
mCapturedTrack->SetHeight(newUpperTrackHeight);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
Track *prev = mTracks->GetPrev(mCapturedTrack);
|
|
|
|
|
|
|
|
double proportion = static_cast < double >(mInitialTrackHeight)
|
|
|
|
/ (mInitialTrackHeight + mInitialUpperTrackHeight);
|
|
|
|
|
|
|
|
int newTrackHeight = static_cast < int >
|
|
|
|
(mInitialTrackHeight + delta * proportion);
|
|
|
|
|
|
|
|
int newUpperTrackHeight = static_cast < int >
|
|
|
|
(mInitialUpperTrackHeight + delta * (1.0 - proportion));
|
|
|
|
|
|
|
|
//make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
if (newUpperTrackHeight < prev->GetMinimizedHeight())
|
|
|
|
newUpperTrackHeight = prev->GetMinimizedHeight();
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight);
|
|
|
|
prev->SetHeight(newUpperTrackHeight);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IsResizingBetweenLinkedTracks:
|
|
|
|
{
|
|
|
|
if(MONO_WAVE_PAN(mCapturedTrack)){
|
|
|
|
int newUpperTrackHeight = mInitialUpperTrackHeight + delta;
|
|
|
|
int newTrackHeight = mInitialTrackHeight - delta;
|
|
|
|
|
|
|
|
// make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight()) {
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
newUpperTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - mCapturedTrack->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
if (newUpperTrackHeight < mCapturedTrack->GetMinimizedHeight()) {
|
|
|
|
newUpperTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
newTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - mCapturedTrack->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
float temp = 1.0f;
|
|
|
|
if(newUpperTrackHeight != 0.0f)
|
|
|
|
temp = (float)newUpperTrackHeight/(float)(newUpperTrackHeight + newTrackHeight);
|
|
|
|
|
|
|
|
mCapturedTrack->SetVirtualTrackPercentage(temp);
|
|
|
|
mCapturedTrack->SetHeight(newUpperTrackHeight);
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight,true);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
Track *next = mTracks->GetNext(mCapturedTrack);
|
|
|
|
int newUpperTrackHeight = mInitialUpperTrackHeight + delta;
|
|
|
|
int newTrackHeight = mInitialTrackHeight - delta;
|
|
|
|
|
|
|
|
// make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < next->GetMinimizedHeight()) {
|
|
|
|
newTrackHeight = next->GetMinimizedHeight();
|
|
|
|
newUpperTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - next->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
if (newUpperTrackHeight < mCapturedTrack->GetMinimizedHeight()) {
|
|
|
|
newUpperTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
newTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - mCapturedTrack->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(newUpperTrackHeight);
|
|
|
|
next->SetHeight(newTrackHeight);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IsResizing:
|
|
|
|
{
|
|
|
|
int newTrackHeight = mInitialTrackHeight + delta;
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// don't refresh in this case.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#else // EXPERIMENTAL_OUTPUT_DISPLAY
|
2010-01-23 19:44:49 +00:00
|
|
|
switch( mMouseCapture )
|
|
|
|
{
|
|
|
|
case IsResizingBelowLinkedTracks:
|
|
|
|
{
|
|
|
|
Track *prev = mTracks->GetPrev(mCapturedTrack);
|
|
|
|
|
|
|
|
double proportion = static_cast < double >(mInitialTrackHeight)
|
|
|
|
/ (mInitialTrackHeight + mInitialUpperTrackHeight);
|
|
|
|
|
|
|
|
int newTrackHeight = static_cast < int >
|
|
|
|
(mInitialTrackHeight + delta * proportion);
|
|
|
|
|
|
|
|
int newUpperTrackHeight = static_cast < int >
|
|
|
|
(mInitialUpperTrackHeight + delta * (1.0 - proportion));
|
|
|
|
|
|
|
|
//make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
if (newUpperTrackHeight < prev->GetMinimizedHeight())
|
|
|
|
newUpperTrackHeight = prev->GetMinimizedHeight();
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight);
|
|
|
|
prev->SetHeight(newUpperTrackHeight);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IsResizingBetweenLinkedTracks:
|
|
|
|
{
|
|
|
|
Track *next = mTracks->GetNext(mCapturedTrack);
|
|
|
|
int newUpperTrackHeight = mInitialUpperTrackHeight + delta;
|
|
|
|
int newTrackHeight = mInitialTrackHeight - delta;
|
|
|
|
|
|
|
|
// make sure neither track is smaller than its minimum height
|
|
|
|
if (newTrackHeight < next->GetMinimizedHeight()) {
|
|
|
|
newTrackHeight = next->GetMinimizedHeight();
|
|
|
|
newUpperTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - next->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
if (newUpperTrackHeight < mCapturedTrack->GetMinimizedHeight()) {
|
|
|
|
newUpperTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
newTrackHeight =
|
|
|
|
mInitialUpperTrackHeight + mInitialTrackHeight - mCapturedTrack->GetMinimizedHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
mCapturedTrack->SetHeight(newUpperTrackHeight);
|
|
|
|
next->SetHeight(newTrackHeight);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IsResizing:
|
|
|
|
{
|
|
|
|
int newTrackHeight = mInitialTrackHeight + delta;
|
|
|
|
if (newTrackHeight < mCapturedTrack->GetMinimizedHeight())
|
|
|
|
newTrackHeight = mCapturedTrack->GetMinimizedHeight();
|
|
|
|
mCapturedTrack->SetHeight(newTrackHeight);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// don't refresh in this case.
|
|
|
|
return;
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif // EXPERIMENTAL_OUTPUT_DISPLAY
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// HandleResize gets called when:
|
|
|
|
/// - A mouse-down event occurs in the "resize region" of a track,
|
|
|
|
/// i.e. to change its vertical height.
|
|
|
|
/// - A mouse event occurs and mIsResizing==true (i.e. while
|
|
|
|
/// the resize is going on)
|
|
|
|
void TrackPanel::HandleResize(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.LeftDown()) {
|
|
|
|
HandleResizeClick( event );
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
|
|
|
else if (event.LeftUp())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
HandleResizeButtonUp( event );
|
|
|
|
}
|
|
|
|
else if (event.Dragging()) {
|
|
|
|
HandleResizeDrag( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-13 20:38:24 +00:00
|
|
|
/// Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling)
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
|
|
|
|
{
|
2010-01-25 22:44:48 +00:00
|
|
|
double steps = event.m_wheelRotation /
|
|
|
|
(event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (event.ShiftDown())
|
|
|
|
{
|
|
|
|
// MM: Scroll left/right when used with Shift key down
|
|
|
|
mListener->TP_ScrollWindow(
|
|
|
|
mViewInfo->h +
|
|
|
|
50.0 * -steps / mViewInfo->zoom);
|
|
|
|
} else if (event.CmdDown())
|
|
|
|
{
|
2012-05-05 21:06:19 +00:00
|
|
|
#if 0
|
|
|
|
// JKC: Alternative scroll wheel zooming code
|
|
|
|
// using AudacityProject zooming, which is smarter,
|
2012-05-04 11:47:29 +00:00
|
|
|
// it keeps selections on screen and centred if it can,
|
|
|
|
// also this ensures mousewheel and zoom buttons give same result.
|
|
|
|
double ZoomFactor = pow(2.0, steps);
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
if( steps > 0 )
|
|
|
|
p->ZoomInByFactor( ZoomFactor );
|
2014-06-03 20:30:19 +00:00
|
|
|
else
|
2012-05-04 11:47:29 +00:00
|
|
|
p->ZoomOutByFactor( ZoomFactor );
|
2012-05-05 21:06:19 +00:00
|
|
|
#endif
|
|
|
|
// MM: Zoom in/out when used with Control key down
|
2014-06-03 20:30:19 +00:00
|
|
|
// We're converting pixel positions to times,
|
2012-05-06 11:50:29 +00:00
|
|
|
// counting pixels from the left edge of the track.
|
2012-05-05 21:06:19 +00:00
|
|
|
int trackLeftEdge = GetLeftOffset();
|
|
|
|
|
2012-05-06 11:50:29 +00:00
|
|
|
// Time corresponding to mouse position
|
2012-05-05 21:06:19 +00:00
|
|
|
double center_h = PositionToTime(event.m_x, trackLeftEdge);
|
2012-05-06 11:50:29 +00:00
|
|
|
// Time corresponding to last (most far right) audio.
|
|
|
|
double audioEndTime = mTracks->GetEndTime();
|
|
|
|
|
|
|
|
// When zooming in in empty space, it's easy to 'lose' the waveform.
|
|
|
|
// This prevents it.
|
|
|
|
// IF zooming in
|
2014-06-03 20:30:19 +00:00
|
|
|
if( steps > 0)
|
2012-05-06 11:50:29 +00:00
|
|
|
{
|
|
|
|
// IF mouse is to right of audio
|
|
|
|
if( center_h > audioEndTime )
|
|
|
|
// Zooming brings far right of audio to mouse.
|
|
|
|
center_h = audioEndTime;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-05-06 11:50:29 +00:00
|
|
|
// Constrain maximum as well as minimum zoom.
|
|
|
|
mViewInfo->zoom = wxMax( gMinZoom, wxMin(mViewInfo->zoom * pow(2.0, steps), gMaxZoom));
|
2012-05-05 21:06:19 +00:00
|
|
|
|
|
|
|
double new_center_h = PositionToTime(event.m_x, trackLeftEdge);
|
|
|
|
mViewInfo->h += (center_h - new_center_h);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
Refresh(false);
|
|
|
|
} else
|
|
|
|
{
|
2012-01-26 22:20:14 +00:00
|
|
|
// MM: Scroll up/down when used without modifier keys
|
2010-01-25 22:44:48 +00:00
|
|
|
double lines = steps * 4 + mVertScrollRemainder;
|
|
|
|
mVertScrollRemainder = lines - floor(lines);
|
|
|
|
lines = floor(lines);
|
|
|
|
mListener->TP_ScrollUpDown((int)-lines);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Filter captured keys typed into LabelTracks.
|
|
|
|
void TrackPanel::OnCaptureKey(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
// Only deal with LabelTracks
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || t->GetKind() != Track::Label) {
|
|
|
|
event.Skip();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
|
|
|
|
|
|
|
|
event.Skip(!((LabelTrack *)t)->CaptureKey(*kevent));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Allow typing into LabelTracks.
|
|
|
|
void TrackPanel::OnKeyDown(wxKeyEvent & event)
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
2014-10-18 14:19:38 +00:00
|
|
|
|
2015-04-09 19:41:32 +00:00
|
|
|
if (event.GetKeyCode() == WXK_ESCAPE) {
|
|
|
|
HandleEscapeKey();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-18 14:19:38 +00:00
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2014-11-08 14:30:19 +00:00
|
|
|
#ifdef SPECTRAL_EDITING_ESC_KEY
|
2014-10-25 22:05:45 +00:00
|
|
|
// Test for pinning and unpinning of the center frequency
|
|
|
|
bool logF;
|
|
|
|
if (mAdjustSelectionEdges &&
|
2014-11-08 14:30:19 +00:00
|
|
|
event.GetKeyCode() == WXK_ESCAPE) {
|
2014-10-25 22:05:45 +00:00
|
|
|
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) {
|
|
|
|
// Toggle center snapping off
|
|
|
|
// (left click can also turn it off)
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
}
|
|
|
|
else if (isSpectrogramTrack(t, &logF)) {
|
|
|
|
WaveTrack *wt = static_cast<WaveTrack*>(t);
|
|
|
|
if (mFreqSelMode == FREQ_SEL_INVALID) {
|
|
|
|
// Toggle center snapping on (the only way to do this)
|
|
|
|
mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
|
|
|
|
StartSnappingFreqSelection(wt);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Handle ESC during frequency drag
|
|
|
|
wxMouseState state(::wxGetMouseState());
|
2014-11-29 22:09:57 +00:00
|
|
|
wxCoord yy = state.GetY();
|
|
|
|
ScreenToClient(NULL, &yy);
|
2014-10-25 22:05:45 +00:00
|
|
|
wxRect r;
|
|
|
|
if (wt == FindTrack(state.GetX(), yy, false, false, &r)) {
|
|
|
|
eFreqSelMode saveMode = mFreqSelMode;
|
|
|
|
mFreqSelMode = FREQ_SEL_PINNED_CENTER;
|
|
|
|
|
|
|
|
StartSnappingFreqSelection(wt);
|
|
|
|
MoveSnappingFreqSelection(yy, r.y, r.height, wt);
|
|
|
|
ExtendFreqSelection(yy, r.y, r.height);
|
|
|
|
UpdateSelectionDisplay();
|
|
|
|
|
|
|
|
mFreqSelMode = saveMode;
|
|
|
|
double hint = -1.0;
|
|
|
|
if (mFreqSelMode == FREQ_SEL_FREE) {
|
|
|
|
hint = PositionToFrequency
|
|
|
|
(true, yy, r.y, r.height, wt->GetRate(), logF);
|
|
|
|
}
|
|
|
|
ResetFreqSelectionPin(hint, logF);
|
|
|
|
// MakeParentModifyState(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// And don't skip event, yet
|
|
|
|
}
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
2014-11-08 14:30:19 +00:00
|
|
|
#endif
|
2014-10-18 14:19:38 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Only deal with LabelTracks
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!t || t->GetKind() != Track::Label) {
|
|
|
|
event.Skip();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LabelTrack *lt = (LabelTrack *)t;
|
2014-10-05 17:10:09 +00:00
|
|
|
double bkpSel0 = mViewInfo->selectedRegion.t0(),
|
|
|
|
bkpSel1 = mViewInfo->selectedRegion.t1();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Pass keystroke to labeltrack's handler and add to history if any
|
|
|
|
// updates were done
|
2014-10-05 17:10:09 +00:00
|
|
|
if (lt->OnKeyDown(mViewInfo->selectedRegion, event))
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Make sure caret is in view
|
|
|
|
int x;
|
|
|
|
if (lt->CalcCursorX(this, &x)) {
|
|
|
|
ScrollIntoView(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If selection modified, refresh
|
|
|
|
// Otherwise, refresh track display if the keystroke was handled
|
2014-10-05 17:10:09 +00:00
|
|
|
if( bkpSel0 != mViewInfo->selectedRegion.t0() ||
|
|
|
|
bkpSel1 != mViewInfo->selectedRegion.t1() )
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh( false );
|
2014-06-03 20:30:19 +00:00
|
|
|
else if (!event.GetSkipped())
|
2010-01-23 19:44:49 +00:00
|
|
|
RefreshTrack(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Allow typing into LabelTracks.
|
|
|
|
void TrackPanel::OnChar(wxKeyEvent & event)
|
|
|
|
{
|
2010-04-14 18:32:03 +00:00
|
|
|
#if defined (__WXGTK__)
|
|
|
|
// AWD: workaround for bug 154
|
|
|
|
bool restore = wxGetApp().inKbdHandler;
|
|
|
|
wxGetApp().inKbdHandler = true;
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Only deal with LabelTracks
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || t->GetKind() != Track::Label) {
|
|
|
|
event.Skip();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
double bkpSel0 = mViewInfo->selectedRegion.t0(),
|
|
|
|
bkpSel1 = mViewInfo->selectedRegion.t1();
|
2010-01-23 19:44:49 +00:00
|
|
|
// Pass keystroke to labeltrack's handler and add to history if any
|
|
|
|
// updates were done
|
2014-10-05 17:10:09 +00:00
|
|
|
if (((LabelTrack *)t)->OnChar(mViewInfo->selectedRegion, event))
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// If selection modified, refresh
|
|
|
|
// Otherwise, refresh track display if the keystroke was handled
|
2014-10-05 17:10:09 +00:00
|
|
|
if( bkpSel0 != mViewInfo->selectedRegion.t0() ||
|
|
|
|
bkpSel1 != mViewInfo->selectedRegion.t1() )
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh( false );
|
2014-06-03 20:30:19 +00:00
|
|
|
else if (!event.GetSkipped())
|
2010-01-23 19:44:49 +00:00
|
|
|
RefreshTrack(t);
|
2010-04-14 18:32:03 +00:00
|
|
|
|
|
|
|
#if defined (__WXGTK__)
|
|
|
|
// AWD: workaround for bug 154
|
|
|
|
wxGetApp().inKbdHandler = restore;
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Should handle the case when the mouse capture is lost.
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxMouseEvent e(wxEVT_LEFT_UP);
|
|
|
|
|
|
|
|
e.m_x = mMouseMostRecentX;
|
|
|
|
e.m_y = mMouseMostRecentY;
|
|
|
|
|
|
|
|
OnMouseEvent(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This handles just generic mouse events. Then, based
|
|
|
|
/// on our current state, we forward the mouse events to
|
|
|
|
/// various interested parties.
|
|
|
|
void TrackPanel::OnMouseEvent(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
if (event.m_wheelRotation != 0)
|
|
|
|
HandleWheelRotation(event);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!mAutoScrolling) {
|
|
|
|
mMouseMostRecentX = event.m_x;
|
|
|
|
mMouseMostRecentY = event.m_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.LeftDown()) {
|
|
|
|
mCapturedTrack = NULL;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// The activate event is used to make the
|
2010-01-23 19:44:49 +00:00
|
|
|
// parent window 'come alive' if it didn't have focus.
|
|
|
|
wxActivateEvent e;
|
2014-10-06 08:10:50 +00:00
|
|
|
GetParent()->GetEventHandler()->ProcessEvent(e);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// wxTimers seem to be a little unreliable, so this
|
|
|
|
// "primes" it to make sure it keeps going for a while...
|
|
|
|
|
|
|
|
// When this timer fires, we call TrackPanel::OnTimer and
|
|
|
|
// possibly update the screen for offscreen scrolling.
|
|
|
|
mTimer.Stop();
|
|
|
|
mTimer.Start(kTimerInterval, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.ButtonDown()) {
|
|
|
|
SetFocus();
|
2015-04-08 21:52:24 +00:00
|
|
|
if (!HasCapture())
|
|
|
|
CaptureMouse();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else if (event.ButtonUp()) {
|
|
|
|
if (HasCapture())
|
|
|
|
ReleaseMouse();
|
|
|
|
}
|
|
|
|
|
2014-12-06 10:15:18 +00:00
|
|
|
if (event.Leaving() && !event.ButtonIsDown(wxMOUSE_BTN_ANY))
|
|
|
|
{
|
|
|
|
SetCapturedTrack(NULL);
|
2014-12-12 21:56:49 +00:00
|
|
|
#if defined(__WXMAC__)
|
|
|
|
// We must install the cursor ourselves since the window under
|
|
|
|
// the mouse is no longer this one and wx2.8.12 makes that check.
|
|
|
|
// Should re-evaluate with wx3.
|
|
|
|
wxSTANDARD_CURSOR->MacInstall();
|
|
|
|
#endif
|
2014-12-06 10:15:18 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
switch( mMouseCapture )
|
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
case IsVZooming:
|
2010-01-23 19:44:49 +00:00
|
|
|
HandleVZoom(event);
|
|
|
|
break;
|
|
|
|
case IsClosing:
|
|
|
|
HandleClosing(event);
|
|
|
|
break;
|
|
|
|
case IsPopping:
|
|
|
|
HandlePopping(event);
|
|
|
|
break;
|
|
|
|
case IsMuting:
|
|
|
|
HandleMutingSoloing(event, false);
|
|
|
|
break;
|
|
|
|
case IsSoloing:
|
|
|
|
HandleMutingSoloing(event, true);
|
|
|
|
break;
|
2014-06-03 20:30:19 +00:00
|
|
|
case IsResizing:
|
2010-01-23 19:44:49 +00:00
|
|
|
case IsResizingBetweenLinkedTracks:
|
|
|
|
case IsResizingBelowLinkedTracks:
|
|
|
|
HandleResize(event);
|
|
|
|
HandleCursor(event);
|
|
|
|
break;
|
|
|
|
case IsRearranging:
|
|
|
|
HandleRearrange(event);
|
|
|
|
break;
|
|
|
|
case IsGainSliding:
|
|
|
|
HandleSliders(event, false);
|
|
|
|
break;
|
|
|
|
case IsPanSliding:
|
|
|
|
HandleSliders(event, true);
|
|
|
|
break;
|
|
|
|
case IsMinimizing:
|
|
|
|
HandleMinimizing(event);
|
|
|
|
break;
|
|
|
|
case IsZooming:
|
|
|
|
HandleZoom(event);
|
|
|
|
break;
|
|
|
|
case IsAdjustingLabel:
|
|
|
|
HandleLabelTrackMouseEvent((LabelTrack *)mCapturedTrack, mCapturedRect, event);
|
|
|
|
break;
|
|
|
|
default: //includes case of IsUncaptured
|
|
|
|
HandleTrackSpecificMouseEvent(event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//EnsureVisible should be called after the up-click.
|
|
|
|
if (event.ButtonUp()) {
|
|
|
|
wxRect r;
|
|
|
|
|
|
|
|
Track *t = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
if (t)
|
|
|
|
EnsureVisible(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TrackPanel::HandleTrackLocationMouseEvent(WaveTrack * track, wxRect &r, wxMouseEvent &event)
|
|
|
|
{
|
2013-08-24 20:30:47 +00:00
|
|
|
// FIXME: Disable this and return true when CutLines aren't showing?
|
2013-08-31 05:57:48 +00:00
|
|
|
// (Don't use gPrefs-> for the fix as registry access is slow).
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (mMouseCapture == WasOverCutLine)
|
|
|
|
{
|
|
|
|
// Needed to avoid select events right after IsOverCutLine event on button up
|
|
|
|
if (event.ButtonUp()) {
|
|
|
|
mMouseCapture = IsUncaptured;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mMouseCapture == IsOverCutLine)
|
|
|
|
{
|
|
|
|
if (!mCapturedTrackLocationRect.Contains(event.m_x, event.m_y))
|
|
|
|
{
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
SetCursorByActivity();
|
|
|
|
return false;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
bool handled = false;
|
|
|
|
|
|
|
|
if (event.LeftDown())
|
|
|
|
{
|
|
|
|
if (mCapturedTrackLocation.typ == WaveTrack::locationCutLine)
|
|
|
|
{
|
|
|
|
// When user presses left button on cut line, expand the line again
|
|
|
|
double cutlineStart = 0, cutlineEnd = 0;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Release capture so user will be able to click OK on Linux
|
|
|
|
if (HasCapture())
|
|
|
|
ReleaseMouse();
|
|
|
|
|
|
|
|
if (track->ExpandCutLine(mCapturedTrackLocation.pos, &cutlineStart, &cutlineEnd))
|
|
|
|
{
|
|
|
|
WaveTrack* linked = (WaveTrack*)mTracks->GetLink(track);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (linked &&
|
2013-10-18 05:03:50 +00:00
|
|
|
!linked->ExpandCutLine(mCapturedTrackLocation.pos))
|
|
|
|
return false;
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setTimes(cutlineStart, cutlineEnd);
|
2010-01-23 19:44:49 +00:00
|
|
|
DisplaySelection();
|
2011-05-22 13:41:01 +00:00
|
|
|
MakeParentPushState(_("Expanded Cut Line"), _("Expand"));
|
2010-01-23 19:44:49 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
} else if (mCapturedTrackLocation.typ == WaveTrack::locationMergePoint)
|
|
|
|
{
|
2013-10-18 05:03:50 +00:00
|
|
|
if (!track->MergeClips(mCapturedTrackLocation.clipidx1, mCapturedTrackLocation.clipidx2))
|
|
|
|
return false;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack* linked = (WaveTrack*)mTracks->GetLink(track);
|
2014-06-03 20:30:19 +00:00
|
|
|
if (linked &&
|
2013-10-18 05:03:50 +00:00
|
|
|
!linked->MergeClips(mCapturedTrackLocation.clipidx1, mCapturedTrackLocation.clipidx2))
|
|
|
|
return false;
|
|
|
|
|
2015-04-07 11:20:50 +00:00
|
|
|
MakeParentPushState(_("Merged Clips"),_("Merge"), PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!handled && event.RightDown())
|
|
|
|
{
|
|
|
|
track->RemoveCutLine(mCapturedTrackLocation.pos);
|
|
|
|
WaveTrack* linked = (WaveTrack*)mTracks->GetLink(track);
|
|
|
|
if (linked)
|
|
|
|
linked->RemoveCutLine(mCapturedTrackLocation.pos);
|
2011-05-22 13:41:01 +00:00
|
|
|
MakeParentPushState(_("Removed Cut Line"), _("Remove") );
|
2010-01-23 19:44:49 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (handled)
|
|
|
|
{
|
|
|
|
SetCapturedTrack( NULL );
|
2014-06-03 20:30:19 +00:00
|
|
|
mMouseCapture = WasOverCutLine;
|
2010-01-23 19:44:49 +00:00
|
|
|
RefreshTrack(track);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i=0; i<track->GetNumCachedLocations(); i++)
|
|
|
|
{
|
|
|
|
WaveTrack::Location loc = track->GetCachedLocation(i);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
double x = (loc.pos - mViewInfo->h) * mViewInfo->zoom;
|
|
|
|
if (x >= 0 && x < r.width)
|
|
|
|
{
|
|
|
|
wxRect locRect;
|
|
|
|
locRect.x = int( r.x + x ) - 5;
|
|
|
|
locRect.width = 11;
|
|
|
|
locRect.y = r.y;
|
|
|
|
locRect.height = r.height;
|
|
|
|
if (locRect.Contains(event.m_x, event.m_y))
|
|
|
|
{
|
|
|
|
SetCapturedTrack(track, IsOverCutLine);
|
|
|
|
mCapturedTrackLocation = loc;
|
|
|
|
mCapturedTrackLocationRect = locRect;
|
|
|
|
SetCursorByActivity();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Event has happened on a track and it has been determined to be a label track.
|
|
|
|
bool TrackPanel::HandleLabelTrackMouseEvent(LabelTrack * lTrack, wxRect &r, wxMouseEvent & event)
|
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
/// \todo This method is one of a large number of methods in
|
2010-01-23 19:44:49 +00:00
|
|
|
/// TrackPanel which suitably modified belong in other classes.
|
2013-10-29 21:49:45 +00:00
|
|
|
if(event.LeftDown())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *n = iter.First();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
while (n) {
|
|
|
|
if (n->GetKind() == Track::Label && lTrack != n) {
|
|
|
|
((LabelTrack *)n)->ResetFlags();
|
|
|
|
((LabelTrack *)n)->Unselect();
|
|
|
|
}
|
|
|
|
n = iter.Next();
|
|
|
|
}
|
|
|
|
|
|
|
|
//If the button was pressed, check to see if we are over
|
2010-09-12 05:11:43 +00:00
|
|
|
//a glyph (this is the second of three calls to the method).
|
2010-01-23 19:44:49 +00:00
|
|
|
//std::cout << ((LabelTrack*)pTrack)->OverGlyph(event.m_x, event.m_y) << std::endl;
|
|
|
|
if(lTrack->OverGlyph(event.m_x, event.m_y))
|
|
|
|
{
|
|
|
|
SetCapturedTrack(lTrack, IsAdjustingLabel);
|
2014-06-03 20:30:19 +00:00
|
|
|
mCapturedRect = r;
|
2010-01-23 19:44:49 +00:00
|
|
|
mCapturedRect.x += kLeftInset;
|
|
|
|
mCapturedRect.width -= kLeftInset;
|
|
|
|
}
|
|
|
|
} else if (event.Dragging()) {
|
|
|
|
;
|
2014-03-31 00:41:16 +00:00
|
|
|
} else if (event.LeftUp() && mCapturedTrack && (mCapturedTrack->GetKind() == Track::Label)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (lTrack->HandleMouse(event, mCapturedRect,
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->h, mViewInfo->zoom, &mViewInfo->selectedRegion)) {
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (event.RightUp()) {
|
|
|
|
// popup menu for editing
|
|
|
|
RefreshTrack(lTrack);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if ((lTrack->getSelectedIndex() != -1) && lTrack->OverTextBox(lTrack->GetLabel(lTrack->getSelectedIndex()), event.m_x, event.m_y)) {
|
|
|
|
mPopupMenuTarget = lTrack;
|
|
|
|
mLabelTrackInfoMenu->Enable(OnCutSelectedTextID, lTrack->IsTextSelected());
|
|
|
|
mLabelTrackInfoMenu->Enable(OnCopySelectedTextID, lTrack->IsTextSelected());
|
|
|
|
mLabelTrackInfoMenu->Enable(OnPasteSelectedTextID, lTrack->IsTextClipSupported());
|
2014-07-12 14:26:07 +00:00
|
|
|
mLabelTrackInfoMenu->Enable(OnDeleteSelectedLabelID, true);
|
2010-01-23 19:44:49 +00:00
|
|
|
PopupMenu(mLabelTrackInfoMenu, event.m_x + 1, event.m_y + 1);
|
2014-12-14 16:01:21 +00:00
|
|
|
mPopupMenuTarget = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
// it's an invalid dragging event
|
|
|
|
lTrack->SetWrongDragging(true);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//If we are adjusting a label on a labeltrack, do not do anything
|
2010-01-23 19:44:49 +00:00
|
|
|
//that follows. Instead, redraw the track.
|
|
|
|
if(mMouseCapture == IsAdjustingLabel)
|
|
|
|
{
|
|
|
|
RefreshTrack(lTrack);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle dragging
|
|
|
|
if(event.Dragging()) {
|
|
|
|
// locate the initial mouse position
|
|
|
|
if (event.LeftIsDown()) {
|
|
|
|
if (mLabelTrackStartXPos == -1) {
|
|
|
|
mLabelTrackStartXPos = event.m_x;
|
|
|
|
mLabelTrackStartYPos = event.m_y;
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
if ((lTrack->getSelectedIndex() != -1) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
lTrack->OverTextBox(
|
2014-06-03 20:30:19 +00:00
|
|
|
lTrack->GetLabel(lTrack->getSelectedIndex()),
|
2010-01-23 19:44:49 +00:00
|
|
|
mLabelTrackStartXPos,
|
2014-06-03 20:30:19 +00:00
|
|
|
mLabelTrackStartYPos))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mLabelTrackStartYPos = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if initial mouse position in the text box
|
|
|
|
// then only drag text
|
|
|
|
if (mLabelTrackStartYPos == -1) {
|
|
|
|
RefreshTrack(lTrack);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// handle mouse left button up
|
|
|
|
if (event.LeftUp()) {
|
|
|
|
mLabelTrackStartXPos = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle shift+ctrl down
|
|
|
|
/*if (event.ShiftDown()) { // && event.ControlDown()) {
|
|
|
|
lTrack->SetHighlightedByKey(true);
|
|
|
|
Refresh(false);
|
|
|
|
return;
|
|
|
|
}*/
|
|
|
|
|
|
|
|
// handle shift+mouse left button
|
|
|
|
if (event.ShiftDown() && event.ButtonDown() && (lTrack->getSelectedIndex() != -1)) {
|
|
|
|
// if the mouse is clicked in text box, set flags
|
|
|
|
if (lTrack->OverTextBox(lTrack->GetLabel(lTrack->getSelectedIndex()), event.m_x, event.m_y)) {
|
|
|
|
lTrack->SetInBox(true);
|
|
|
|
lTrack->SetDragXPos(event.m_x);
|
|
|
|
lTrack->SetResetCursorPos(true);
|
|
|
|
RefreshTrack(lTrack);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// return false, there is more to do...
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// AS: I don't really understand why this code is sectioned off
|
|
|
|
// from the other OnMouseEvent code.
|
|
|
|
void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
Track * pTrack;
|
|
|
|
wxRect r;
|
|
|
|
wxRect rLabel;
|
|
|
|
|
|
|
|
AudacityProject *p = GetProject();
|
|
|
|
bool unsafe = (p->GetAudioIOToken()>0 &&
|
|
|
|
gAudioIO->IsStreamActive(p->GetAudioIOToken()));
|
|
|
|
|
|
|
|
FindTrack(event.m_x, event.m_y, true, true, &rLabel);
|
|
|
|
pTrack = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//call HandleResize if I'm over the border area
|
2010-01-23 19:44:49 +00:00
|
|
|
if (event.LeftDown() &&
|
|
|
|
(within(event.m_y, r.y + r.height, TRACK_RESIZE_REGION)
|
|
|
|
|| within(event.m_y, rLabel.y + rLabel.height,
|
|
|
|
TRACK_RESIZE_REGION))) {
|
|
|
|
HandleResize(event);
|
|
|
|
HandleCursor(event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Determine if user clicked on the track's left-hand label
|
|
|
|
if (!mCapturedTrack && event.m_x < GetLeftOffset()) {
|
|
|
|
if (event.m_x >= GetVRulerOffset()) {
|
|
|
|
if( !event.Dragging() ) // JKC: Only want the mouse down event.
|
|
|
|
HandleVZoom(event);
|
|
|
|
HandleCursor(event);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
HandleLabelClick(event);
|
|
|
|
HandleCursor(event);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Determine if user clicked on a label track.
|
|
|
|
//If so, use MouseDown handler for the label track.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pTrack && (pTrack->GetKind() == Track::Label))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if(HandleLabelTrackMouseEvent( (LabelTrack *) pTrack, r, event ))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
if (pTrack && (pTrack->GetKind() == Track::Wave) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
(mMouseCapture == IsUncaptured || mMouseCapture == IsOverCutLine ||
|
|
|
|
mMouseCapture == WasOverCutLine))
|
|
|
|
{
|
|
|
|
if (HandleTrackLocationMouseEvent( (WaveTrack *) pTrack, r, event ))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ToolsToolBar * pTtb = mListener->TP_GetToolsToolBar();
|
|
|
|
if( pTtb == NULL )
|
|
|
|
return;
|
|
|
|
|
|
|
|
int toolToUse = DetermineToolToUse(pTtb, event);
|
|
|
|
|
|
|
|
switch (toolToUse) {
|
|
|
|
case selectTool:
|
|
|
|
HandleSelect(event);
|
|
|
|
break;
|
|
|
|
case envelopeTool:
|
|
|
|
if (!unsafe)
|
|
|
|
HandleEnvelope(event);
|
|
|
|
break;
|
|
|
|
case slideTool:
|
|
|
|
if (!unsafe)
|
|
|
|
HandleSlide(event);
|
|
|
|
break;
|
|
|
|
case zoomTool:
|
|
|
|
HandleZoom(event);
|
|
|
|
break;
|
|
|
|
case drawTool:
|
|
|
|
if (!unsafe)
|
|
|
|
HandleSampleEditing(event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((event.Moving() || event.LeftUp()) &&
|
|
|
|
(mMouseCapture == IsUncaptured ))
|
2014-06-03 20:30:19 +00:00
|
|
|
// (mMouseCapture != IsSelecting ) &&
|
2010-01-23 19:44:49 +00:00
|
|
|
// (mMouseCapture != IsEnveloping) &&
|
|
|
|
// (mMouseCapture != IsSliding) )
|
|
|
|
{
|
|
|
|
HandleCursor(event);
|
|
|
|
}
|
|
|
|
if (event.LeftUp()) {
|
|
|
|
mCapturedTrack = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// If we are in multimode, looks at the type of track and where we are on it to
|
2010-01-23 19:44:49 +00:00
|
|
|
/// determine what object we are hovering over and hence what tool to use.
|
|
|
|
/// @param pTtb - A pointer to the tools tool bar
|
|
|
|
/// @param event - Mouse event, with info about position and what mouse buttons are down.
|
|
|
|
int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
int currentTool = pTtb->GetCurrentTool();
|
|
|
|
|
|
|
|
// Unless in Multimode keep using the current tool.
|
|
|
|
if( !pTtb->IsDown(multiTool) )
|
|
|
|
return currentTool;
|
|
|
|
|
|
|
|
// We NEVER change tools whilst we are dragging.
|
|
|
|
if( event.Dragging() || event.LeftUp() )
|
|
|
|
return currentTool;
|
|
|
|
|
|
|
|
// Just like dragging.
|
|
|
|
// But, this event might be the final button up
|
|
|
|
// so keep the same tool.
|
|
|
|
// if( mIsSliding || mIsSelecting || mIsEnveloping )
|
|
|
|
if( mMouseCapture != IsUncaptured )
|
|
|
|
return currentTool;
|
|
|
|
|
|
|
|
// So now we have to find out what we are near to..
|
|
|
|
wxRect r;
|
|
|
|
|
|
|
|
Track *pTrack = FindTrack(event.m_x, event.m_y, false, false, &r);
|
|
|
|
if( !pTrack )
|
|
|
|
return currentTool;
|
|
|
|
|
|
|
|
int trackKind = pTrack->GetKind();
|
|
|
|
currentTool = selectTool; // the default.
|
|
|
|
|
2014-10-06 08:10:50 +00:00
|
|
|
if (event.ButtonIsDown(wxMOUSE_BTN_RIGHT) || event.RightUp()){
|
2010-01-23 19:44:49 +00:00
|
|
|
currentTool = zoomTool;
|
|
|
|
} else if( trackKind == Track::Time ){
|
|
|
|
currentTool = envelopeTool;
|
|
|
|
} else if( trackKind == Track::Label ){
|
|
|
|
currentTool = selectTool;
|
|
|
|
} else if( trackKind != Track::Wave) {
|
|
|
|
currentTool = selectTool;
|
|
|
|
// So we are in a wave track.
|
2013-08-24 20:30:47 +00:00
|
|
|
//FIXME: Not necessarily. Haven't checked Track::Note (#if defined(USE_MIDI)).
|
2014-06-03 20:30:19 +00:00
|
|
|
// From here on the order in which we hit test determines
|
2010-01-23 19:44:49 +00:00
|
|
|
// which tool takes priority in the rare cases where it
|
|
|
|
// could be more than one.
|
|
|
|
} else if (event.CmdDown()){
|
|
|
|
// msmeyer: If control is down, slide single clip
|
|
|
|
// msmeyer: If control and shift are down, slide all clips
|
|
|
|
currentTool = slideTool;
|
|
|
|
} else if( HitTestEnvelope( pTrack, r, event ) ){
|
|
|
|
currentTool = envelopeTool;
|
|
|
|
} else if( HitTestSlide( pTrack, r, event )){
|
|
|
|
currentTool = slideTool;
|
|
|
|
} else if( HitTestSamples( pTrack, r, event )){
|
|
|
|
currentTool = drawTool;
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
//Use the false argument since in multimode we don't
|
2010-01-23 19:44:49 +00:00
|
|
|
//want the button indicating which tool is in use to be updated.
|
|
|
|
pTtb->SetCurrentTool( currentTool, false );
|
|
|
|
return currentTool;
|
|
|
|
}
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
bool TrackPanel::HitTestStretch(Track *track, wxRect &r, wxMouseEvent & event)
|
|
|
|
{
|
|
|
|
// later, we may want a different policy, but for now, stretch is
|
|
|
|
// selected when the cursor is near the center of the track and
|
|
|
|
// within the selection
|
|
|
|
if (!track || !track->GetSelected() || track->GetKind() != Track::Note ||
|
|
|
|
gAudioIO->IsStreamActive( GetProject()->GetAudioIOToken())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
int center = r.y + r.height / 2;
|
|
|
|
int distance = abs(event.m_y - center);
|
|
|
|
const int yTolerance = 10;
|
2014-10-05 17:10:09 +00:00
|
|
|
wxInt64 leftSel = TimeToPosition(mViewInfo->selectedRegion.t0(), r.x);
|
|
|
|
wxInt64 rightSel = TimeToPosition(mViewInfo->selectedRegion.t1(), r.x);
|
2010-09-18 21:02:36 +00:00
|
|
|
// Something is wrong if right edge comes before left edge
|
|
|
|
wxASSERT(!(rightSel < leftSel));
|
|
|
|
return (leftSel <= event.m_x && event.m_x <= rightSel &&
|
|
|
|
distance < yTolerance);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// method that tells us if the mouse event landed on an
|
2010-01-23 19:44:49 +00:00
|
|
|
/// envelope boundary.
|
|
|
|
bool TrackPanel::HitTestEnvelope(Track *track, wxRect &r, wxMouseEvent & event)
|
|
|
|
{
|
2011-11-03 22:19:50 +00:00
|
|
|
wxASSERT(track);
|
2011-04-23 18:53:48 +00:00
|
|
|
if( track->GetKind() != Track::Wave )
|
|
|
|
return false;
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack *wavetrack = (WaveTrack *)track;
|
|
|
|
Envelope *envelope = wavetrack->GetEnvelopeAtX(event.GetX());
|
|
|
|
|
|
|
|
if (!envelope)
|
2013-10-29 21:49:45 +00:00
|
|
|
return false;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
int displayType = wavetrack->GetDisplay();
|
2014-06-03 20:30:19 +00:00
|
|
|
// Not an envelope hit, unless we're using a type of wavetrack display
|
2010-01-23 19:44:49 +00:00
|
|
|
// suitable for envelopes operations, ie one of the Wave displays.
|
2014-06-03 20:30:19 +00:00
|
|
|
if ( displayType > 1)
|
2010-01-23 19:44:49 +00:00
|
|
|
return false; // No envelope, not a hit, so return.
|
|
|
|
|
|
|
|
// Get envelope point, range 0.0 to 1.0
|
|
|
|
bool dB = (displayType == 1);
|
2014-06-03 20:30:19 +00:00
|
|
|
double envValue = envelope->GetValueAtX(
|
2010-01-23 19:44:49 +00:00
|
|
|
event.m_x, r, mViewInfo->h, mViewInfo->zoom );
|
|
|
|
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
wavetrack->GetDisplayBounds(&zoomMin, &zoomMax);
|
|
|
|
|
|
|
|
// Get y position of envelope point.
|
|
|
|
int yValue = GetWaveYPos( envValue,
|
2013-10-29 21:49:45 +00:00
|
|
|
zoomMin, zoomMax,
|
2010-01-23 19:44:49 +00:00
|
|
|
r.height, dB, true, mdBr, false ) + r.y;
|
|
|
|
|
|
|
|
// Get y position of center line
|
|
|
|
int ctr = GetWaveYPos( 0.0,
|
2013-10-29 21:49:45 +00:00
|
|
|
zoomMin, zoomMax,
|
2010-01-23 19:44:49 +00:00
|
|
|
r.height, dB, true, mdBr, false ) + r.y;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Get y distance of mouse from center line (in pixels).
|
|
|
|
int yMouse = abs(ctr - event.m_y);
|
|
|
|
// Get y distance of envelope from center line (in pixels)
|
|
|
|
yValue = abs(ctr-yValue);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// JKC: It happens that the envelope is actually drawn offset from its
|
2010-01-23 19:44:49 +00:00
|
|
|
// 'true' position (it is 3 pixels wide). yMisalign is really a fudge
|
2014-06-03 20:30:19 +00:00
|
|
|
// factor to allow us to hit it exactly, but I wouldn't dream of
|
2010-01-23 19:44:49 +00:00
|
|
|
// calling it yFudgeFactor :)
|
2014-06-03 20:30:19 +00:00
|
|
|
const int yMisalign = 2;
|
2010-01-23 19:44:49 +00:00
|
|
|
// Perhaps yTolerance should be put into preferences?
|
|
|
|
const int yTolerance = 5; // how far from envelope we may be and count as a hit.
|
|
|
|
int distance;
|
|
|
|
|
|
|
|
// For amplification using the envelope we introduced the idea of contours.
|
|
|
|
// The contours have the same shape as the envelope, which may be partially off-screen.
|
|
|
|
// The contours are closer in to the center line.
|
|
|
|
int ContourSpacing = (int) (r.height / (2* (zoomMax-zoomMin)));
|
|
|
|
const int MaxContours = 2;
|
|
|
|
|
|
|
|
// Adding ContourSpacing/2 selects a region either side of the contour.
|
|
|
|
int yDisplace = yValue - yMisalign - yMouse + ContourSpacing/2;
|
|
|
|
if (yDisplace > (MaxContours * ContourSpacing))
|
|
|
|
return false;
|
|
|
|
// Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
|
|
|
|
distance = abs( ( yDisplace % ContourSpacing ) - ContourSpacing/2);
|
|
|
|
return( distance < yTolerance );
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// method that tells us if the mouse event landed on an
|
2010-01-23 19:44:49 +00:00
|
|
|
/// editable sample
|
|
|
|
bool TrackPanel::HitTestSamples(Track *track, wxRect &r, wxMouseEvent & event)
|
|
|
|
{
|
2011-11-03 22:19:50 +00:00
|
|
|
wxASSERT(track);
|
2011-04-23 18:53:48 +00:00
|
|
|
if( track->GetKind() != Track::Wave )
|
|
|
|
return false;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack *wavetrack = (WaveTrack *)track;
|
|
|
|
//Get rate in order to calculate the critical zoom threshold
|
|
|
|
double rate = wavetrack->GetRate();
|
|
|
|
|
|
|
|
//Find out the zoom level
|
|
|
|
bool showPoints = (mViewInfo->zoom / rate > 3.0);
|
|
|
|
if( !showPoints )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
int displayType = wavetrack->GetDisplay();
|
2014-06-03 20:30:19 +00:00
|
|
|
if ( displayType > 1)
|
2010-01-23 19:44:49 +00:00
|
|
|
return false; // Not a wave, so return.
|
|
|
|
|
|
|
|
float oneSample;
|
|
|
|
double pps = mViewInfo->zoom;
|
|
|
|
double tt = (event.m_x - r.x) / pps + mViewInfo->h;
|
2011-04-26 14:54:41 +00:00
|
|
|
sampleCount s0 = (sampleCount)(tt * rate + 0.5);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Just get one sample.
|
|
|
|
wavetrack->Get((samplePtr)&oneSample, floatSample, s0, 1);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Get y distance of envelope point from center line (in pixels).
|
|
|
|
bool dB = (displayType == 1);
|
|
|
|
float zoomMin, zoomMax;
|
|
|
|
|
|
|
|
wavetrack->GetDisplayBounds(&zoomMin, &zoomMax);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
double envValue = 1.0;
|
|
|
|
Envelope* env = wavetrack->GetEnvelopeAtX(event.GetX());
|
|
|
|
if (env)
|
|
|
|
envValue = env->GetValue(tt);
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
int yValue = GetWaveYPos( oneSample * envValue,
|
2013-10-29 21:49:45 +00:00
|
|
|
zoomMin, zoomMax,
|
|
|
|
r.height, dB, true, mdBr, false) + r.y;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Get y position of mouse (in pixels)
|
|
|
|
int yMouse = event.m_y;
|
|
|
|
|
|
|
|
// Perhaps yTolerance should be put into preferences?
|
|
|
|
const int yTolerance = 10; // More tolerance on samples than on envelope.
|
2013-10-29 21:49:45 +00:00
|
|
|
return( abs( yValue - yMouse ) < yTolerance );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// method that tells us if the mouse event landed on a
|
2010-01-23 19:44:49 +00:00
|
|
|
/// time-slider that allows us to time shift the sequence.
|
2013-08-25 21:51:26 +00:00
|
|
|
bool TrackPanel::HitTestSlide(Track * WXUNUSED(track), wxRect &r, wxMouseEvent & event)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Perhaps we should delegate this to TrackArtist as only TrackArtist
|
|
|
|
// knows what the real sizes are??
|
|
|
|
|
|
|
|
// The drag Handle width includes border, width and a little extra margin.
|
|
|
|
const int adjustedDragHandleWidth = 14;
|
2014-06-03 20:30:19 +00:00
|
|
|
// The hotspot for the cursor isn't at its centre. Adjust for this.
|
2010-01-23 19:44:49 +00:00
|
|
|
const int hotspotOffset = 5;
|
|
|
|
|
|
|
|
// We are doing an approximate test here - is the mouse in the right or left border?
|
|
|
|
if( event.m_x + hotspotOffset < r.x + adjustedDragHandleWidth)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if( event.m_x + hotspotOffset > r.x + r.width - adjustedDragHandleWidth)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
double TrackPanel::GetMostRecentXPos()
|
|
|
|
{
|
|
|
|
return mViewInfo->h +
|
|
|
|
(mMouseMostRecentX - GetLabelWidth()) / mViewInfo->zoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
|
|
|
|
{
|
2011-06-04 02:41:16 +00:00
|
|
|
Track *link = trk->GetLink();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
if (link && !trk->GetLinked()) {
|
|
|
|
trk = link;
|
|
|
|
link = trk->GetLink();
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
wxRect r(kLeftInset,
|
|
|
|
-mViewInfo->vpos + trk->GetY() + kTopInset,
|
|
|
|
GetRect().GetWidth() - kLeftInset * 2 - 1,
|
|
|
|
trk->GetHeight() - kTopInset - 1);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
if (link) {
|
|
|
|
r.height += link->GetHeight();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
else if(MONO_WAVE_PAN(trk)){
|
|
|
|
r.height += trk->GetHeight(true);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if( refreshbacking )
|
|
|
|
{
|
|
|
|
mRefreshBacking = true;
|
|
|
|
}
|
|
|
|
|
2011-06-04 02:41:16 +00:00
|
|
|
Refresh( false, &r );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// This method overrides Refresh() of wxWindow so that the
|
2011-06-04 02:41:16 +00:00
|
|
|
/// boolean play indictaor can be set to false, so that an old play indicator that is
|
2014-06-03 20:30:19 +00:00
|
|
|
/// no longer there won't get XORed (to erase it), thus redrawing it on the
|
2010-01-23 19:44:49 +00:00
|
|
|
/// TrackPanel
|
|
|
|
void TrackPanel::Refresh(bool eraseBackground /* = TRUE */,
|
|
|
|
const wxRect *rect /* = NULL */)
|
|
|
|
{
|
|
|
|
// Tell OnPaint() to refresh the backing bitmap.
|
|
|
|
//
|
|
|
|
// Originally I had the check within the OnPaint() routine and it
|
|
|
|
// was working fine. That was until I found that, even though a full
|
|
|
|
// refresh was requested, Windows only set the onscreen portion of a
|
|
|
|
// window as damaged.
|
|
|
|
//
|
|
|
|
// So, if any part of the trackpanel was off the screen, full refreshes
|
|
|
|
// didn't work and the display got corrupted.
|
|
|
|
if( !rect || ( *rect == GetRect() ) )
|
|
|
|
{
|
|
|
|
mRefreshBacking = true;
|
|
|
|
}
|
|
|
|
wxWindow::Refresh(eraseBackground, rect);
|
|
|
|
DisplaySelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw the actual track areas. We only draw the borders
|
|
|
|
/// and the little buttons and menues and whatnot here, the
|
|
|
|
/// actual contents of each track are drawn by the TrackArtist.
|
|
|
|
void TrackPanel::DrawTracks(wxDC * dc)
|
|
|
|
{
|
|
|
|
wxRegion region = GetUpdateRegion();
|
|
|
|
|
|
|
|
wxRect clip = GetRect();
|
|
|
|
|
|
|
|
wxRect panelRect = clip;
|
|
|
|
panelRect.y = -mViewInfo->vpos;
|
|
|
|
|
|
|
|
wxRect tracksRect = panelRect;
|
|
|
|
tracksRect.x += GetLabelWidth();
|
|
|
|
tracksRect.width -= GetLabelWidth();
|
|
|
|
|
|
|
|
ToolsToolBar *pTtb = mListener->TP_GetToolsToolBar();
|
|
|
|
bool bMultiToolDown = pTtb->IsDown(multiTool);
|
|
|
|
bool envelopeFlag = pTtb->IsDown(envelopeTool) || bMultiToolDown;
|
|
|
|
bool samplesFlag = pTtb->IsDown(drawTool) || bMultiToolDown;
|
|
|
|
bool sliderFlag = bMultiToolDown;
|
|
|
|
|
|
|
|
// The track artist actually draws the stuff inside each track
|
|
|
|
mTrackArtist->DrawTracks(mTracks, GetProject()->GetFirstVisible(),
|
2014-06-03 20:30:19 +00:00
|
|
|
*dc, region, tracksRect, clip, mViewInfo,
|
2010-01-23 19:44:49 +00:00
|
|
|
envelopeFlag, samplesFlag, sliderFlag);
|
|
|
|
|
|
|
|
DrawEverythingElse(dc, region, panelRect, clip);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Draws 'Everything else'. In particular it draws:
|
|
|
|
/// - Drop shadow for tracks and vertical rulers.
|
|
|
|
/// - Zooming Indicators.
|
2014-06-03 20:30:19 +00:00
|
|
|
/// - Fills in space below the tracks.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::DrawEverythingElse(wxDC * dc,
|
|
|
|
const wxRegion region,
|
2013-08-25 21:51:26 +00:00
|
|
|
const wxRect WXUNUSED(panelRect),
|
2010-01-23 19:44:49 +00:00
|
|
|
const wxRect clip)
|
|
|
|
{
|
|
|
|
// We draw everything else
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
wxRect focusRect(-1, -1, 0, 0);
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect trackRect = clip;
|
|
|
|
trackRect.height = 0; // for drawing background in no tracks case.
|
|
|
|
|
|
|
|
VisibleTrackIterator iter(GetProject());
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
|
|
|
trackRect.y = t->GetY() - mViewInfo->vpos;
|
|
|
|
trackRect.height = t->GetHeight();
|
|
|
|
|
|
|
|
// If this track is linked to the next one, display a common
|
|
|
|
// border for both, otherwise draw a normal border
|
|
|
|
wxRect r = trackRect;
|
|
|
|
bool skipBorder = false;
|
|
|
|
Track *l = t->GetLink();
|
|
|
|
|
|
|
|
if (t->GetLinked()) {
|
|
|
|
r.height += l->GetHeight();
|
|
|
|
}
|
|
|
|
else if (l && trackRect.y >= 0) {
|
|
|
|
skipBorder = true;
|
|
|
|
}
|
|
|
|
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(MONO_WAVE_PAN(t)){
|
|
|
|
r.height += t->GetHeight(true);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// If the previous track is linked to this one but isn't on the screen
|
|
|
|
// (and thus would have been skipped by VisibleTrackIterator) we need to
|
|
|
|
// draw that track's border instead.
|
|
|
|
Track *borderTrack = t;
|
|
|
|
wxRect borderRect = r, borderTrackRect = trackRect;
|
|
|
|
|
|
|
|
if (l && !t->GetLinked() && trackRect.y < 0)
|
|
|
|
{
|
|
|
|
borderTrack = l;
|
|
|
|
|
|
|
|
borderTrackRect.y = l->GetY() - mViewInfo->vpos;
|
|
|
|
borderTrackRect.height = l->GetHeight();
|
|
|
|
|
|
|
|
borderRect = borderTrackRect;
|
|
|
|
borderRect.height += t->GetHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!skipBorder) {
|
|
|
|
if (mAx->IsFocused(t)) {
|
|
|
|
focusRect = borderRect;
|
|
|
|
}
|
|
|
|
DrawOutside(borderTrack, dc, borderRect, borderTrackRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Believe it or not, we can speed up redrawing if we don't
|
|
|
|
// redraw the vertical ruler when only the waveform data has
|
|
|
|
// changed. An example is during recording.
|
|
|
|
|
|
|
|
#if DEBUG_DRAW_TIMING
|
|
|
|
// wxRect rbox = region.GetBox();
|
|
|
|
// wxPrintf(wxT("Update Region: %d %d %d %d\n"),
|
|
|
|
// rbox.x, rbox.y, rbox.width, rbox.height);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (region.Contains(0, trackRect.y, GetLeftOffset(), trackRect.height)) {
|
|
|
|
wxRect r = trackRect;
|
|
|
|
r.x += GetVRulerOffset();
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.width = GetVRulerWidth();
|
|
|
|
r.height -= (kTopInset + 2);
|
|
|
|
mTrackArtist->DrawVRuler(t, dc, r);
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(MONO_WAVE_PAN(t)){
|
|
|
|
trackRect.y = t->GetY(true) - mViewInfo->vpos;
|
|
|
|
trackRect.height = t->GetHeight(true);
|
|
|
|
if (region.Contains(0, trackRect.y, GetLeftOffset(), trackRect.height)) {
|
|
|
|
wxRect r = trackRect;
|
|
|
|
r.x += GetVRulerOffset();
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.width = GetVRulerWidth();
|
|
|
|
r.height -= (kTopInset + 2);
|
|
|
|
mTrackArtist->DrawVRuler(t, dc, r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((mMouseCapture == IsZooming || mMouseCapture == IsVZooming) &&
|
2010-09-18 21:02:36 +00:00
|
|
|
IsDragZooming()
|
|
|
|
// note track zooming now works like audio track
|
|
|
|
//#ifdef USE_MIDI
|
|
|
|
// && mCapturedTrack && mCapturedTrack->GetKind() != Track::Note
|
|
|
|
//#endif
|
|
|
|
) {
|
2010-01-23 19:44:49 +00:00
|
|
|
DrawZooming(dc, clip);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Paint over the part below the tracks
|
|
|
|
trackRect.y += trackRect.height;
|
|
|
|
if (trackRect.y < clip.GetBottom()) {
|
|
|
|
AColor::TrackPanelBackground(dc, false);
|
|
|
|
dc->DrawRectangle(trackRect.x,
|
|
|
|
trackRect.y,
|
|
|
|
trackRect.width,
|
|
|
|
clip.height - trackRect.y);
|
|
|
|
}
|
|
|
|
|
2011-10-19 23:06:53 +00:00
|
|
|
// Sometimes highlight is not drawn on backing bitmap. I thought
|
2014-06-03 20:30:19 +00:00
|
|
|
// it was because FindFocus did not return "this" on Mac, but
|
2011-10-19 23:06:53 +00:00
|
|
|
// when I removed that test, yielding this condition:
|
|
|
|
// if (GetFocusedTrack() != NULL) {
|
|
|
|
// the highlight was reportedly drawn even when something else
|
|
|
|
// was the focus and no highlight should be drawn. -RBD
|
|
|
|
if (GetFocusedTrack() != NULL && wxWindow::FindFocus() == this) {
|
2010-01-23 19:44:49 +00:00
|
|
|
HighlightFocusedTrack(dc, focusRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw snap guidelines if we have any
|
|
|
|
if (mSnapManager && (mSnapLeft >= 0 || mSnapRight >= 0)) {
|
|
|
|
AColor::SnapGuidePen(dc);
|
|
|
|
if (mSnapLeft >= 0) {
|
|
|
|
AColor::Line(*dc, (int)mSnapLeft, 0, mSnapLeft, 30000);
|
|
|
|
}
|
|
|
|
if (mSnapRight >= 0) {
|
|
|
|
AColor::Line(*dc, (int)mSnapRight, 0, mSnapRight, 30000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw zooming indicator that shows the region that will
|
|
|
|
/// be zoomed into when the user clicks and drags with a
|
|
|
|
/// zoom cursor. Handles both vertical and horizontal
|
|
|
|
/// zooming.
|
|
|
|
void TrackPanel::DrawZooming(wxDC * dc, const wxRect clip)
|
|
|
|
{
|
|
|
|
wxRect r;
|
|
|
|
|
|
|
|
dc->SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
dc->SetPen(*wxBLACK_DASHED_PEN);
|
|
|
|
|
|
|
|
if (mMouseCapture==IsVZooming) {
|
|
|
|
int width, height;
|
|
|
|
GetTracksUsableArea(&width, &height);
|
|
|
|
|
|
|
|
r.y = mZoomStart;
|
|
|
|
r.x = GetVRulerOffset();
|
|
|
|
r.width = width + GetVRulerWidth() + 1; //+1 extends into border rect
|
|
|
|
r.height = mZoomEnd - mZoomStart;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
r.x = mZoomStart;
|
|
|
|
r.y = -1;
|
|
|
|
r.width = mZoomEnd - mZoomStart;
|
|
|
|
r.height = clip.height + 2;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
dc->DrawRectangle(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TrackPanel::DrawOutside(Track * t, wxDC * dc, const wxRect rec,
|
|
|
|
const wxRect trackRect)
|
|
|
|
{
|
|
|
|
wxRect r = rec;
|
|
|
|
int labelw = GetLabelWidth();
|
|
|
|
int vrul = GetVRulerOffset();
|
|
|
|
|
|
|
|
DrawOutsideOfTrack(t, dc, r);
|
|
|
|
|
|
|
|
r.x += kLeftInset;
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.width -= kLeftInset * 2;
|
|
|
|
r.height -= kTopInset;
|
|
|
|
|
|
|
|
mTrackInfo.SetTrackInfoFont(dc);
|
|
|
|
dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
|
|
|
|
|
|
|
|
bool bIsWave = (t->GetKind() == Track::Wave);
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
bool bIsNote = (t->GetKind() == Track::Note);
|
|
|
|
#endif
|
2014-06-03 20:30:19 +00:00
|
|
|
// don't enable bHasMuteSolo for Note track because it will draw in the
|
2010-09-18 21:02:36 +00:00
|
|
|
// wrong place.
|
2010-03-09 04:59:26 +00:00
|
|
|
mTrackInfo.DrawBackground(dc, r, t->GetSelected(), bIsWave, labelw, vrul);
|
|
|
|
|
2010-08-24 19:32:54 +00:00
|
|
|
// Vaughan, 2010-08-24: No longer doing this.
|
2010-08-11 23:15:00 +00:00
|
|
|
// Draw sync-lock tiles in ruler area.
|
2010-08-24 19:32:54 +00:00
|
|
|
//if (t->IsSyncLockSelected()) {
|
|
|
|
// wxRect tileFill = r;
|
|
|
|
// tileFill.x = GetVRulerOffset();
|
|
|
|
// tileFill.width = GetVRulerWidth();
|
|
|
|
// TrackArtist::DrawSyncLockTiles(dc, tileFill);
|
|
|
|
//}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
DrawBordersAroundTrack(t, dc, r, labelw, vrul);
|
|
|
|
DrawShadow(t, dc, r);
|
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
r.width = mTrackInfo.GetTrackInfoWidth();
|
2010-01-23 19:44:49 +00:00
|
|
|
bool captured = (t == mCapturedTrack);
|
|
|
|
mTrackInfo.DrawCloseBox(dc, r, (captured && mMouseCapture==IsClosing));
|
|
|
|
mTrackInfo.DrawTitleBar(dc, r, t, (captured && mMouseCapture==IsPopping));
|
|
|
|
|
2010-08-25 22:34:17 +00:00
|
|
|
mTrackInfo.DrawMinimize(dc, r, t, (captured && mMouseCapture==IsMinimizing));
|
2010-09-16 23:08:33 +00:00
|
|
|
|
|
|
|
// Draw the sync-lock indicator if this track is in a sync-lock selected group.
|
2014-06-03 20:30:19 +00:00
|
|
|
if (t->IsSyncLockSelected())
|
2010-09-16 23:08:33 +00:00
|
|
|
{
|
|
|
|
wxRect syncLockIconRect;
|
|
|
|
mTrackInfo.GetSyncLockIconRect(r, syncLockIconRect);
|
2014-06-03 20:30:19 +00:00
|
|
|
wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockIcon));
|
2010-09-16 23:08:33 +00:00
|
|
|
// Icon is 12x12 and syncLockIconRect is 16x16.
|
2014-06-03 20:30:19 +00:00
|
|
|
dc->DrawBitmap(syncLockBitmap,
|
|
|
|
syncLockIconRect.x + 3,
|
|
|
|
syncLockIconRect.y + 2,
|
2010-09-16 23:08:33 +00:00
|
|
|
true);
|
|
|
|
}
|
|
|
|
|
2010-09-14 05:52:01 +00:00
|
|
|
mTrackInfo.DrawBordersWithin( dc, r, bIsWave );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (bIsWave) {
|
|
|
|
mTrackInfo.DrawMuteSolo(dc, r, t, (captured && mMouseCapture == IsMuting), false, HasSoloButton());
|
|
|
|
mTrackInfo.DrawMuteSolo(dc, r, t, (captured && mMouseCapture == IsSoloing), true, HasSoloButton());
|
|
|
|
|
|
|
|
mTrackInfo.DrawSliders(dc, (WaveTrack *)t, r);
|
|
|
|
if (!t->GetMinimized()) {
|
|
|
|
int offset = 8;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (r.y + 22 + 12 < rec.y + rec.height - 19)
|
|
|
|
dc->DrawText(TrackSubText(t),
|
|
|
|
trackRect.x + offset,
|
|
|
|
trackRect.y + 22);
|
2013-05-30 23:14:25 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (r.y + 38 + 12 < rec.y + rec.height - 19)
|
|
|
|
dc->DrawText(GetSampleFormatStr(((WaveTrack *) t)->GetSampleFormat()),
|
|
|
|
trackRect.x + offset,
|
|
|
|
trackRect.y + 38);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef USE_MIDI
|
2010-09-18 21:02:36 +00:00
|
|
|
else if (bIsNote) {
|
2014-06-03 20:30:19 +00:00
|
|
|
// Note tracks do not have text, e.g. "Mono, 44100Hz, 32-bit float", so
|
|
|
|
// Mute & Solo button goes higher. To preserve existing AudioTrack code,
|
2010-09-18 21:02:36 +00:00
|
|
|
// we move the buttons up by pretending track is higher (at lower y)
|
|
|
|
r.y -= 34;
|
|
|
|
r.height += 34;
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect midiRect;
|
|
|
|
mTrackInfo.GetTrackControlsRect(trackRect, midiRect);
|
2010-09-18 21:02:36 +00:00
|
|
|
// Offset by height of Solo/Mute buttons:
|
|
|
|
midiRect.y += 15;
|
|
|
|
midiRect.height -= 21; // allow room for minimize button at bottom
|
|
|
|
|
2014-10-25 22:05:45 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2011-02-07 20:24:04 +00:00
|
|
|
// the offset 2 is just to leave a little space between channel buttons
|
|
|
|
// and velocity slider (if any)
|
|
|
|
int h = ((NoteTrack *) t)->DrawLabelControls(*dc, midiRect) + 2;
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
// Draw some lines for MuteSolo buttons:
|
|
|
|
if (r.height > 84) {
|
2014-06-03 20:30:19 +00:00
|
|
|
AColor::Line(*dc, r.x+48 , r.y+50, r.x+48, r.y + 66);
|
2010-09-18 21:02:36 +00:00
|
|
|
// bevel below mute/solo
|
|
|
|
AColor::Line(*dc, r.x, r.y + 66, mTrackInfo.GetTrackInfoWidth(), r.y + 66);
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
mTrackInfo.DrawMuteSolo(dc, r, t,
|
2010-09-18 21:02:36 +00:00
|
|
|
(captured && mMouseCapture == IsMuting), false, HasSoloButton());
|
2014-06-03 20:30:19 +00:00
|
|
|
mTrackInfo.DrawMuteSolo(dc, r, t,
|
2010-09-18 21:02:36 +00:00
|
|
|
(captured && mMouseCapture == IsSoloing), true, HasSoloButton());
|
|
|
|
|
|
|
|
// place a volume control below channel buttons (this will
|
|
|
|
// control an offset to midi velocity).
|
|
|
|
// DrawVelocitySlider places slider assuming this is a Wave track
|
|
|
|
// and using a large offset to leave room for other things,
|
|
|
|
// so here we make a fake rectangle as if it is for a Wave
|
|
|
|
// track, but it is offset to place the slider properly in
|
|
|
|
// a Note track. This whole placement thing should be redesigned
|
|
|
|
// to lay out different types of tracks and controls
|
|
|
|
wxRect gr; // gr is gain rectangle where slider is drawn
|
|
|
|
mTrackInfo.GetGainRect(r, gr);
|
|
|
|
r.y = r.y + h - gr.y; // ultimately want slider at r.y + h
|
|
|
|
r.height = r.height - h + gr.y;
|
|
|
|
// save for mouse hit detect:
|
2014-06-03 20:30:19 +00:00
|
|
|
((NoteTrack *) t)->SetGainPlacementRect(r);
|
2010-09-18 21:02:36 +00:00
|
|
|
mTrackInfo.DrawVelocitySlider(dc, (NoteTrack *) t, r);
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-10-25 22:05:45 +00:00
|
|
|
#endif // USE_MIDI
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::DrawOutsideOfTrack(Track * t, wxDC * dc, const wxRect r)
|
|
|
|
{
|
|
|
|
// Fill in area outside of the track
|
|
|
|
AColor::TrackPanelBackground(dc, false);
|
|
|
|
wxRect side;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Area between panel border and left track border
|
|
|
|
side = r;
|
|
|
|
side.width = kLeftInset;
|
|
|
|
dc->DrawRectangle(side);
|
|
|
|
|
|
|
|
// Area between panel border and top track border
|
|
|
|
side = r;
|
|
|
|
side.height = kTopInset;
|
|
|
|
dc->DrawRectangle(side);
|
|
|
|
|
|
|
|
// Area between panel border and right track border
|
|
|
|
side = r;
|
|
|
|
side.x += side.width - kTopInset;
|
|
|
|
side.width = kTopInset;
|
|
|
|
dc->DrawRectangle(side);
|
|
|
|
|
|
|
|
// Area between tracks of stereo group
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if (t->GetLinked() || MONO_WAVE_PAN(t)) {
|
|
|
|
side = r;
|
|
|
|
side.y += t->GetHeight() - 1;
|
|
|
|
side.height = kTopInset + 1;
|
|
|
|
dc->DrawRectangle(side);
|
|
|
|
}
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t->GetLinked()) {
|
|
|
|
side = r;
|
|
|
|
side.y += t->GetHeight() - 1;
|
|
|
|
side.height = kTopInset + 1;
|
|
|
|
dc->DrawRectangle(side);
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw a three-level highlight gradient around the focused track.
|
|
|
|
void TrackPanel::HighlightFocusedTrack(wxDC * dc, const wxRect r)
|
|
|
|
{
|
|
|
|
wxRect rect = r;
|
|
|
|
rect.x += kLeftInset;
|
|
|
|
rect.y += kTopInset;
|
|
|
|
rect.width -= kLeftInset * 2;
|
|
|
|
rect.height -= kTopInset;
|
|
|
|
|
|
|
|
dc->SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
|
|
|
|
AColor::TrackFocusPen(dc, 0);
|
|
|
|
dc->DrawRectangle(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2);
|
|
|
|
|
|
|
|
AColor::TrackFocusPen(dc, 1);
|
|
|
|
dc->DrawRectangle(rect.x - 2, rect.y - 2, rect.width + 4, rect.height + 4);
|
|
|
|
|
|
|
|
AColor::TrackFocusPen(dc, 2);
|
|
|
|
dc->DrawRectangle(rect.x - 3, rect.y - 3, rect.width + 6, rect.height + 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::UpdateVRulers()
|
|
|
|
{
|
|
|
|
TrackListOfKindIterator iter(Track::Wave, mTracks);
|
|
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
|
|
|
UpdateTrackVRuler(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateVRulerSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::UpdateVRuler(Track *t)
|
|
|
|
{
|
|
|
|
UpdateTrackVRuler(t);
|
|
|
|
|
|
|
|
UpdateVRulerSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::UpdateTrackVRuler(Track *t)
|
|
|
|
{
|
2011-11-10 20:17:44 +00:00
|
|
|
wxASSERT(t);
|
2011-11-20 07:09:46 +00:00
|
|
|
if (!t)
|
|
|
|
return;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxRect r(GetVRulerOffset(),
|
|
|
|
kTopInset,
|
|
|
|
GetVRulerWidth(),
|
|
|
|
t->GetHeight() - (kTopInset + 2));
|
|
|
|
|
2011-11-20 07:09:46 +00:00
|
|
|
mTrackArtist->UpdateVRuler(t, r);
|
|
|
|
Track *l = t->GetLink();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (l)
|
2011-11-20 07:09:46 +00:00
|
|
|
{
|
|
|
|
r.height = l->GetHeight() - (kTopInset + 2);
|
|
|
|
mTrackArtist->UpdateVRuler(l, r);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
else if(MONO_WAVE_PAN(t)){
|
|
|
|
r.height = t->GetHeight(true) - (kTopInset + 2);
|
|
|
|
mTrackArtist->UpdateVRuler(t, r);
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::UpdateVRulerSize()
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *t = iter.First();
|
|
|
|
if (t) {
|
|
|
|
wxSize s = t->vrulerSize;
|
|
|
|
while (t) {
|
|
|
|
s.IncTo(t->vrulerSize);
|
|
|
|
t = iter.Next();
|
|
|
|
}
|
|
|
|
if (vrulerSize != s) {
|
|
|
|
vrulerSize = s;
|
|
|
|
mRuler->SetLeftOffset(GetLeftOffset()); // bevel on AdornedRuler
|
|
|
|
mRuler->Refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// The following method moves to the previous track
|
2010-01-23 19:44:49 +00:00
|
|
|
/// selecting and unselecting depending if you are on the start of a
|
|
|
|
/// block or not.
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// \todo Merge related methods, TrackPanel::OnPrevTrack and
|
2010-01-23 19:44:49 +00:00
|
|
|
/// TrackPanel::OnNextTrack.
|
|
|
|
void TrackPanel::OnPrevTrack( bool shift )
|
|
|
|
{
|
|
|
|
TrackListIterator iter( mTracks );
|
2011-11-24 23:12:52 +00:00
|
|
|
Track* t = GetFocusedTrack();
|
2010-01-23 19:44:49 +00:00
|
|
|
if( t == NULL ) // if there isn't one, focus on last
|
|
|
|
{
|
|
|
|
t = iter.Last();
|
|
|
|
SetFocusedTrack( t );
|
|
|
|
EnsureVisible( t );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-24 23:12:52 +00:00
|
|
|
Track* p = NULL;
|
|
|
|
bool tSelected = false;
|
|
|
|
bool pSelected = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
if( shift )
|
|
|
|
{
|
|
|
|
p = mTracks->GetPrev( t, true ); // Get previous track
|
|
|
|
if( p == NULL ) // On first track
|
|
|
|
{
|
2011-11-25 21:26:01 +00:00
|
|
|
// JKC: wxBell() is probably for accessibility, so a blind
|
|
|
|
// user knows they were at the top track.
|
|
|
|
wxBell();
|
2010-01-23 19:44:49 +00:00
|
|
|
if( mCircularTrackNavigation )
|
|
|
|
{
|
|
|
|
TrackListIterator iter( mTracks );
|
2011-11-25 21:26:01 +00:00
|
|
|
p = iter.Last();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
EnsureVisible( t );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tSelected = t->GetSelected();
|
2011-11-24 23:12:52 +00:00
|
|
|
if (p)
|
|
|
|
pSelected = p->GetSelected();
|
2010-01-23 19:44:49 +00:00
|
|
|
if( tSelected && pSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( t, false );
|
|
|
|
SetFocusedTrack( p ); // move focus to next track down
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( tSelected && !pSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( p, true );
|
|
|
|
SetFocusedTrack( p ); // move focus to next track down
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( !tSelected && pSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( p, false );
|
|
|
|
SetFocusedTrack( p ); // move focus to next track down
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( !tSelected && !pSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( t, true );
|
|
|
|
SetFocusedTrack( p ); // move focus to next track down
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
p = mTracks->GetPrev( t, true ); // Get next track
|
|
|
|
if( p == NULL ) // On last track so stay there?
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
if( mCircularTrackNavigation )
|
|
|
|
{
|
|
|
|
TrackListIterator iter( mTracks );
|
|
|
|
for( Track *d = iter.First(); d; d = iter.Next( true ) )
|
|
|
|
{
|
|
|
|
p = d;
|
|
|
|
}
|
|
|
|
SetFocusedTrack( p ); // Wrap to the first track
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
EnsureVisible( t );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetFocusedTrack( p ); // move focus to next track down
|
|
|
|
EnsureVisible( p );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// The following method moves to the next track,
|
2010-01-23 19:44:49 +00:00
|
|
|
/// selecting and unselecting depending if you are on the start of a
|
|
|
|
/// block or not.
|
|
|
|
void TrackPanel::OnNextTrack( bool shift )
|
|
|
|
{
|
|
|
|
Track *t;
|
|
|
|
Track *n;
|
|
|
|
TrackListIterator iter( mTracks );
|
|
|
|
bool tSelected,nSelected;
|
|
|
|
|
|
|
|
t = GetFocusedTrack(); // Get currently focused track
|
|
|
|
if( t == NULL ) // if there isn't one, focus on first
|
|
|
|
{
|
|
|
|
t = iter.First();
|
|
|
|
SetFocusedTrack( t );
|
|
|
|
EnsureVisible( t );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( shift )
|
|
|
|
{
|
|
|
|
n = mTracks->GetNext( t, true ); // Get next track
|
|
|
|
if( n == NULL ) // On last track so stay there
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
if( mCircularTrackNavigation )
|
|
|
|
{
|
|
|
|
TrackListIterator iter( mTracks );
|
|
|
|
n = iter.First();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
EnsureVisible( t );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tSelected = t->GetSelected();
|
|
|
|
nSelected = n->GetSelected();
|
|
|
|
if( tSelected && nSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( t, false );
|
|
|
|
SetFocusedTrack( n ); // move focus to next track down
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( tSelected && !nSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( n, true );
|
|
|
|
SetFocusedTrack( n ); // move focus to next track down
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( !tSelected && nSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( n, false );
|
|
|
|
SetFocusedTrack( n ); // move focus to next track down
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( !tSelected && !nSelected )
|
|
|
|
{
|
|
|
|
mTracks->Select( t, true );
|
|
|
|
SetFocusedTrack( n ); // move focus to next track down
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
n = mTracks->GetNext( t, true ); // Get next track
|
|
|
|
if( n == NULL ) // On last track so stay there
|
|
|
|
{
|
|
|
|
wxBell();
|
|
|
|
if( mCircularTrackNavigation )
|
|
|
|
{
|
|
|
|
TrackListIterator iter( mTracks );
|
|
|
|
n = iter.First();
|
|
|
|
SetFocusedTrack( n ); // Wrap to the first track
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
EnsureVisible( t );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetFocusedTrack( n ); // move focus to next track down
|
|
|
|
EnsureVisible( n );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnToggle()
|
|
|
|
{
|
|
|
|
Track *t;
|
|
|
|
|
|
|
|
t = GetFocusedTrack(); // Get currently focused track
|
|
|
|
if (!t)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mTracks->Select( t, !t->GetSelected() );
|
|
|
|
EnsureVisible( t );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2012-03-21 03:09:06 +00:00
|
|
|
|
|
|
|
mAx->Updated();
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-29 21:49:45 +00:00
|
|
|
// Make sure selection edge is in view
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::ScrollIntoView(double pos)
|
|
|
|
{
|
|
|
|
int w, h;
|
|
|
|
GetTracksUsableArea( &w, &h );
|
|
|
|
|
|
|
|
if( ( pos < mViewInfo->h ) ||
|
|
|
|
( pos > mViewInfo->h + ( ( w - 1 ) / mViewInfo->zoom ) ) )
|
|
|
|
{
|
|
|
|
mListener->TP_ScrollWindow( pos - ( ( w / 2 ) / mViewInfo->zoom ) );
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::ScrollIntoView(int x)
|
|
|
|
{
|
|
|
|
ScrollIntoView(PositionToTime(x, GetLeftOffset()));
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:01:14 +00:00
|
|
|
void TrackPanel::OnCursorLeft( bool shift, bool ctrl, bool keyup )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
// PRL: What I found and preserved, strange though it be:
|
|
|
|
// During playback: jump depends on preferences and is independent of the zoom
|
|
|
|
// and does not vary if the key is held
|
|
|
|
// Else: jump depends on the zoom and gets bigger if the key is held
|
|
|
|
int snapToTime = GetActiveProject()->GetSnapTo();
|
|
|
|
double quietSeekStepPositive = 1.0 / mViewInfo->zoom;
|
|
|
|
double audioSeekStepPositive = shift ? mSeekLong : mSeekShort;
|
|
|
|
SeekLeftOrRight
|
|
|
|
(true, shift, ctrl, keyup, snapToTime, true, false,
|
|
|
|
quietSeekStepPositive, audioSeekStepPositive);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnCursorRight(bool shift, bool ctrl, bool keyup)
|
|
|
|
{
|
|
|
|
// PRL: What I found and preserved, strange though it be:
|
|
|
|
// During playback: jump depends on preferences and is independent of the zoom
|
|
|
|
// and does not vary if the key is held
|
|
|
|
// Else: jump depends on the zoom and gets bigger if the key is held
|
|
|
|
int snapToTime = GetActiveProject()->GetSnapTo();
|
|
|
|
double quietSeekStepPositive = 1.0 / mViewInfo->zoom;
|
|
|
|
double audioSeekStepPositive = shift ? mSeekLong : mSeekShort;
|
|
|
|
SeekLeftOrRight
|
|
|
|
(false, shift, ctrl, keyup, snapToTime, true, false,
|
|
|
|
quietSeekStepPositive, audioSeekStepPositive);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle small cursor and play head movements
|
|
|
|
void TrackPanel::SeekLeftOrRight
|
|
|
|
(bool leftward, bool shift, bool ctrl, bool keyup,
|
|
|
|
int snapToTime, bool mayAccelerateQuiet, bool mayAccelerateAudio,
|
|
|
|
double quietSeekStepPositive, double audioSeekStepPositive)
|
|
|
|
{
|
|
|
|
if (keyup)
|
2013-10-23 18:01:14 +00:00
|
|
|
{
|
|
|
|
int token = GetProject()->GetAudioIOToken();
|
2014-11-29 22:09:57 +00:00
|
|
|
if (token > 0 && gAudioIO->IsStreamActive(token))
|
2013-10-23 18:01:14 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2013-10-23 18:01:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// If the last adjustment was very recent, we are
|
|
|
|
// holding the key down and should move faster.
|
2014-11-29 22:09:57 +00:00
|
|
|
const wxLongLong curtime = ::wxGetLocalTimeMillis();
|
|
|
|
enum { MIN_INTERVAL = 50 };
|
|
|
|
const bool fast = (curtime - mLastSelectionAdjustment < MIN_INTERVAL);
|
|
|
|
|
|
|
|
// How much faster should the cursor move if shift is down?
|
|
|
|
enum { LARGER_MULTIPLIER = 4 };
|
|
|
|
int multiplier = (fast && mayAccelerateQuiet) ? LARGER_MULTIPLIER : 1;
|
|
|
|
if (leftward)
|
|
|
|
multiplier = -multiplier;
|
2010-02-02 19:43:52 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
int token = GetProject()->GetAudioIOToken();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
if (shift && ctrl)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
mLastSelectionAdjustment = curtime;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Contract selection
|
|
|
|
// Reduce and constrain (counter-intuitive)
|
|
|
|
if (leftward) {
|
|
|
|
mViewInfo->selectedRegion.setT1(
|
|
|
|
std::max(mViewInfo->selectedRegion.t0(),
|
|
|
|
snapToTime
|
|
|
|
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
|
|
|
|
: mViewInfo->selectedRegion.t1() +
|
|
|
|
multiplier * quietSeekStepPositive));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Make sure it's visible.
|
|
|
|
ScrollIntoView(mViewInfo->selectedRegion.t1());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
else {
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(
|
2014-11-29 22:09:57 +00:00
|
|
|
std::min(mViewInfo->selectedRegion.t1(),
|
|
|
|
snapToTime
|
|
|
|
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
|
|
|
|
: mViewInfo->selectedRegion.t0() +
|
|
|
|
multiplier * quietSeekStepPositive));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Make sure new position is in view.
|
|
|
|
ScrollIntoView(mViewInfo->selectedRegion.t0());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
Refresh(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
else if (token > 0 && gAudioIO->IsStreamActive(token)) {
|
|
|
|
#ifdef EXPERIMENTAL_IMPROVED_SEEKING
|
|
|
|
if (gAudioIO->GetLastPlaybackTime() < mLastSelectionAdjustment) {
|
|
|
|
// Allow time for the last seek to output a buffer before
|
|
|
|
// discarding samples again
|
|
|
|
// Do not advance mLastSelectionAdjustment
|
2013-10-23 18:01:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
#endif
|
|
|
|
mLastSelectionAdjustment = curtime;
|
2013-10-23 18:01:14 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Ignore the multiplier for the quiet case
|
|
|
|
multiplier = (fast && mayAccelerateAudio) ? LARGER_MULTIPLIER : 1;
|
|
|
|
if (leftward)
|
|
|
|
multiplier = -multiplier;
|
|
|
|
|
|
|
|
// If playing, reposition
|
|
|
|
gAudioIO->SeekStream(multiplier * audioSeekStepPositive);
|
2013-10-23 18:01:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
else if (shift)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
mLastSelectionAdjustment = curtime;
|
2010-02-02 19:43:52 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Extend selection
|
|
|
|
// Expand and constrain
|
|
|
|
if (leftward) {
|
|
|
|
mViewInfo->selectedRegion.setT0(
|
|
|
|
std::max(0.0,
|
|
|
|
snapToTime
|
2014-10-05 17:10:09 +00:00
|
|
|
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
|
|
|
|
: mViewInfo->selectedRegion.t0() +
|
2014-11-29 22:09:57 +00:00
|
|
|
multiplier * quietSeekStepPositive));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Make sure it's visible.
|
|
|
|
ScrollIntoView(mViewInfo->selectedRegion.t0());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
else {
|
|
|
|
double end = mTracks->GetEndTime();
|
|
|
|
mViewInfo->selectedRegion.setT1(
|
|
|
|
std::min(end,
|
|
|
|
snapToTime
|
|
|
|
? GridMove(mViewInfo->selectedRegion.t1(), multiplier)
|
|
|
|
: mViewInfo->selectedRegion.t1() +
|
|
|
|
multiplier * quietSeekStepPositive));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Make sure new position is in view.
|
|
|
|
ScrollIntoView(mViewInfo->selectedRegion.t1());
|
|
|
|
}
|
|
|
|
Refresh(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
mLastSelectionAdjustment = curtime;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
// Move the cursor
|
2010-01-23 19:44:49 +00:00
|
|
|
// Already in cursor mode?
|
2014-10-05 17:10:09 +00:00
|
|
|
if (mViewInfo->selectedRegion.isPoint())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Move and constrain
|
|
|
|
double end = mTracks->GetEndTime();
|
2014-11-29 22:09:57 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(
|
|
|
|
std::max(0.0,
|
|
|
|
std::min(end,
|
|
|
|
snapToTime
|
|
|
|
? GridMove(mViewInfo->selectedRegion.t0(), multiplier)
|
|
|
|
: mViewInfo->selectedRegion.t0() + multiplier * quietSeekStepPositive
|
|
|
|
)
|
|
|
|
),
|
2014-10-05 17:10:09 +00:00
|
|
|
false);
|
2014-11-29 22:09:57 +00:00
|
|
|
mViewInfo->selectedRegion.collapseToT0();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Move the visual cursor
|
|
|
|
DrawCursor();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2010-09-14 05:52:01 +00:00
|
|
|
// Transition to cursor mode.
|
2014-11-29 22:09:57 +00:00
|
|
|
if (leftward)
|
|
|
|
mViewInfo->selectedRegion.collapseToT0();
|
|
|
|
else
|
|
|
|
mViewInfo->selectedRegion.collapseToT1();
|
|
|
|
Refresh(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure new position is in view
|
2014-11-29 22:09:57 +00:00
|
|
|
ScrollIntoView(mViewInfo->selectedRegion.t1());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-02 19:43:52 +00:00
|
|
|
// Handles moving a selection edge with the keyboard in snap-to-time mode;
|
|
|
|
// returns the moved value.
|
|
|
|
// Will move at least minPix pixels -- set minPix positive to move forward,
|
|
|
|
// negative to move backward.
|
|
|
|
double TrackPanel::GridMove(double t, int minPix)
|
|
|
|
{
|
2014-11-08 15:18:43 +00:00
|
|
|
NumericTextCtrl ttc(NumericConverter::TIME, this, wxID_ANY, wxT(""), 0.0, GetProject()->GetRate());
|
2013-10-27 01:58:35 +00:00
|
|
|
ttc.SetFormatName(GetProject()->GetSelectionFormat());
|
2014-11-08 15:18:43 +00:00
|
|
|
ttc.SetValue(t);
|
2010-02-02 19:43:52 +00:00
|
|
|
|
|
|
|
// Try incrementing/decrementing the value; if we've moved far enough we're
|
|
|
|
// done
|
|
|
|
double result;
|
|
|
|
minPix >= 0 ? ttc.Increment() : ttc.Decrement();
|
2014-11-08 15:18:43 +00:00
|
|
|
result = ttc.GetValue();
|
2010-02-03 15:54:37 +00:00
|
|
|
if (fabs(result - t) * mViewInfo->zoom >= fabs((double)minPix)) {
|
2010-02-02 19:43:52 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, move minPix pixels, then snap to the time.
|
|
|
|
result = t + minPix / mViewInfo->zoom;
|
2014-11-08 15:18:43 +00:00
|
|
|
ttc.SetValue(result);
|
|
|
|
result = ttc.GetValue();
|
2010-02-02 19:43:52 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::OnBoundaryMove(bool left, bool boundaryContract)
|
|
|
|
{
|
|
|
|
// Move the left/right selection boundary, to either expand or contract the selection
|
|
|
|
// left=true: operate on left boundary; left=false: operate on right boundary
|
|
|
|
// boundaryContract=true: contract region; boundaryContract=false: expand region.
|
|
|
|
|
|
|
|
// If the last adjustment was very recent, we are
|
|
|
|
// holding the key down and should move faster.
|
|
|
|
wxLongLong curtime = ::wxGetLocalTimeMillis();
|
|
|
|
int multiplier = 1;
|
|
|
|
if( curtime - mLastSelectionAdjustment < 50 )
|
|
|
|
{
|
|
|
|
multiplier = 4;
|
|
|
|
}
|
|
|
|
mLastSelectionAdjustment = curtime;
|
|
|
|
|
|
|
|
int token = GetProject()->GetAudioIOToken();
|
|
|
|
if( token > 0 && gAudioIO->IsStreamActive( token ) )
|
|
|
|
{
|
|
|
|
double indicator = gAudioIO->GetStreamTime();
|
|
|
|
if (left) {
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(indicator, false);
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
|
|
|
{
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT1(indicator);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
else
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
// BOUNDARY MOVEMENT
|
|
|
|
// Contract selection from the right to the left
|
|
|
|
if( boundaryContract )
|
|
|
|
{
|
|
|
|
if (left) {
|
|
|
|
// Reduce and constrain left boundary (counter-intuitive)
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(
|
|
|
|
std::min(mViewInfo->selectedRegion.t1(),
|
|
|
|
mViewInfo->selectedRegion.t0() +
|
|
|
|
multiplier / mViewInfo->zoom));
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Make sure it's visible
|
2014-10-05 17:10:09 +00:00
|
|
|
ScrollIntoView( mViewInfo->selectedRegion.t0() );
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Reduce and constrain right boundary (counter-intuitive)
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT1(
|
|
|
|
std::max(mViewInfo->selectedRegion.t0(),
|
|
|
|
mViewInfo->selectedRegion.t1() -
|
|
|
|
multiplier / mViewInfo->zoom));
|
2010-01-23 19:44:49 +00:00
|
|
|
// Make sure it's visible
|
2014-10-05 17:10:09 +00:00
|
|
|
ScrollIntoView( mViewInfo->selectedRegion.t1() );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// BOUNDARY MOVEMENT
|
|
|
|
// Extend selection toward the left
|
2014-06-03 20:30:19 +00:00
|
|
|
else
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (left) {
|
|
|
|
// Expand and constrain left boundary
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT0(
|
|
|
|
std::max(0.0,
|
|
|
|
mViewInfo->selectedRegion.t0() -
|
|
|
|
multiplier / mViewInfo->zoom));
|
2010-01-23 19:44:49 +00:00
|
|
|
// Make sure it's visible
|
2014-10-05 17:10:09 +00:00
|
|
|
ScrollIntoView( mViewInfo->selectedRegion.t0() );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Expand and constrain right boundary
|
|
|
|
double end = mTracks->GetEndTime();
|
2014-10-05 17:10:09 +00:00
|
|
|
mViewInfo->selectedRegion.setT1(
|
|
|
|
std::min(end,
|
|
|
|
mViewInfo->selectedRegion.t1() +
|
|
|
|
multiplier/mViewInfo->zoom));
|
2014-10-18 14:19:38 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
Refresh( false );
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Move the cursor forward or backward, while paused or while playing.
|
2010-01-23 19:44:49 +00:00
|
|
|
// forward=true: Move cursor forward; forward=false: Move cursor backwards
|
|
|
|
// jump=false: Move cursor determined by zoom; jump=true: Use seek times
|
|
|
|
// longjump=false: Use mSeekShort; longjump=true: Use mSeekLong
|
|
|
|
void TrackPanel::OnCursorMove(bool forward, bool jump, bool longjump )
|
|
|
|
{
|
2014-11-29 22:09:57 +00:00
|
|
|
// PRL: nobody calls this yet with !jump
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
double positiveSeekStep;
|
2010-01-23 19:44:49 +00:00
|
|
|
if (jump) {
|
|
|
|
if (!longjump) {
|
2014-11-29 22:09:57 +00:00
|
|
|
positiveSeekStep = mSeekShort;
|
2010-01-23 19:44:49 +00:00
|
|
|
} else {
|
2014-11-29 22:09:57 +00:00
|
|
|
positiveSeekStep = mSeekLong;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
2014-11-29 22:09:57 +00:00
|
|
|
positiveSeekStep = 1.0 / mViewInfo->zoom;
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2014-11-29 22:09:57 +00:00
|
|
|
bool mayAccelerate = !jump;
|
|
|
|
SeekLeftOrRight
|
|
|
|
(!forward, false, false, false,
|
|
|
|
0, mayAccelerate, mayAccelerate,
|
|
|
|
positiveSeekStep, positiveSeekStep);
|
2014-10-05 17:10:09 +00:00
|
|
|
|
2014-11-29 22:09:57 +00:00
|
|
|
MakeParentModifyState(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
//The following methods operate controls on specified tracks,
|
2010-01-23 19:44:49 +00:00
|
|
|
//This will pop up the track panning dialog for specified track
|
|
|
|
void TrackPanel::OnTrackPan()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.PanSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
if (slider->ShowDialog()) {
|
|
|
|
SetTrackPan(t, slider);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackPanLeft()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.PanSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
slider->Decrease(1);
|
|
|
|
SetTrackPan(t, slider);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackPanRight()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.PanSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
slider->Increase(1);
|
|
|
|
SetTrackPan(t, slider);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::SetTrackPan(Track * t, LWSlider * s)
|
|
|
|
{
|
2011-11-03 22:19:50 +00:00
|
|
|
wxASSERT(t);
|
2011-04-23 18:53:48 +00:00
|
|
|
if( t->GetKind() != Track::Wave )
|
|
|
|
return;
|
2010-01-23 19:44:49 +00:00
|
|
|
float newValue = s->Get();
|
|
|
|
|
|
|
|
WaveTrack *link = (WaveTrack *)mTracks->GetLink(t);
|
|
|
|
((WaveTrack*)t)->SetPan(newValue);
|
|
|
|
if (link)
|
|
|
|
link->SetPan(newValue);
|
|
|
|
|
2011-05-22 13:41:01 +00:00
|
|
|
MakeParentPushState(_("Adjusted Pan"), _("Pan"), PUSH_CONSOLIDATE );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
RefreshTrack(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This will pop up the track gain dialog for specified track
|
|
|
|
void TrackPanel::OnTrackGain()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.GainSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
if (slider->ShowDialog()) {
|
|
|
|
SetTrackGain(t, slider);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackGainInc()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.GainSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
slider->Increase(1);
|
|
|
|
SetTrackGain(t, slider);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackGainDec()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if (!t || (t->GetKind() != Track::Wave)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider *slider = mTrackInfo.GainSlider(t->GetIndex());
|
2010-01-23 19:44:49 +00:00
|
|
|
slider->Decrease(1);
|
|
|
|
SetTrackGain(t, slider);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::SetTrackGain(Track * t, LWSlider * s)
|
|
|
|
{
|
2011-11-03 22:19:50 +00:00
|
|
|
wxASSERT(t);
|
2011-04-23 18:53:48 +00:00
|
|
|
if( t->GetKind() != Track::Wave )
|
|
|
|
return ;
|
2010-01-23 19:44:49 +00:00
|
|
|
float newValue = s->Get();
|
|
|
|
|
|
|
|
WaveTrack *link = (WaveTrack *)mTracks->GetLink(t);
|
|
|
|
((WaveTrack*)t)->SetGain(newValue);
|
|
|
|
if (link)
|
|
|
|
link->SetGain(newValue);
|
|
|
|
|
2011-05-22 13:41:01 +00:00
|
|
|
MakeParentPushState(_("Adjusted gain"), _("Gain"), PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
RefreshTrack(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackMenu(Track *t)
|
|
|
|
{
|
|
|
|
if(!t) {
|
|
|
|
t = GetFocusedTrack();
|
|
|
|
if(!t) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPopupMenuTarget = t;
|
|
|
|
|
|
|
|
bool canMakeStereo = false;
|
|
|
|
Track *next = mTracks->GetNext(t);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxMenu *theMenu = NULL;
|
2012-12-19 21:49:25 +00:00
|
|
|
if (t->GetKind() == Track::Time) {
|
2010-01-23 19:44:49 +00:00
|
|
|
theMenu = mTimeTrackMenu;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
TimeTrack *tt = (TimeTrack*) t;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
theMenu->Enable(OnTimeTrackLinID, tt->GetDisplayLog());
|
|
|
|
theMenu->Enable(OnTimeTrackLogID, !tt->GetDisplayLog());
|
|
|
|
theMenu->Check(OnTimeTrackLogIntID, tt->GetInterpolateLog());
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t->GetKind() == Track::Wave) {
|
|
|
|
theMenu = mWaveTrackMenu;
|
|
|
|
if (next && !t->GetLinked() && !next->GetLinked()
|
|
|
|
&& t->GetKind() == Track::Wave
|
|
|
|
&& next->GetKind() == Track::Wave)
|
|
|
|
canMakeStereo = true;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2013-11-03 01:17:58 +00:00
|
|
|
theMenu->Enable(OnSwapChannelsID, t->GetLinked());
|
2010-01-23 19:44:49 +00:00
|
|
|
theMenu->Enable(OnMergeStereoID, canMakeStereo);
|
|
|
|
theMenu->Enable(OnSplitStereoID, t->GetLinked());
|
|
|
|
theMenu->Enable(OnSplitStereoMonoID, t->GetLinked());
|
|
|
|
theMenu->Check(OnChannelMonoID, t->GetChannel() == Track::MonoChannel);
|
|
|
|
theMenu->Check(OnChannelLeftID, t->GetChannel() == Track::LeftChannel);
|
|
|
|
theMenu->Check(OnChannelRightID, t->GetChannel() == Track::RightChannel);
|
|
|
|
theMenu->Enable(OnChannelMonoID, !t->GetLinked());
|
|
|
|
theMenu->Enable(OnChannelLeftID, !t->GetLinked());
|
|
|
|
theMenu->Enable(OnChannelRightID, !t->GetLinked());
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int display = ((WaveTrack *) t)->GetDisplay();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
theMenu->Enable(OnWaveformID, display != WaveTrack::WaveformDisplay);
|
|
|
|
theMenu->Enable(OnWaveformDBID,
|
|
|
|
display != WaveTrack::WaveformDBDisplay);
|
|
|
|
theMenu->Enable(OnSpectrumID, display != WaveTrack::SpectrumDisplay);
|
|
|
|
theMenu->Enable(OnSpectrumLogID, display != WaveTrack::SpectrumLogDisplay);
|
|
|
|
theMenu->Enable(OnPitchID, display != WaveTrack::PitchDisplay);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack * track = (WaveTrack *)t;
|
|
|
|
SetMenuCheck(*mRateMenu, IdOfRate((int) track->GetRate()));
|
|
|
|
SetMenuCheck(*mFormatMenu, IdOfFormat(track->GetSampleFormat()));
|
|
|
|
|
|
|
|
bool unsafe = IsUnsafe();
|
|
|
|
for (int i = OnRate8ID; i <= OnFloatID; i++) {
|
|
|
|
theMenu->Enable(i, !unsafe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(USE_MIDI)
|
|
|
|
if (t->GetKind() == Track::Note)
|
|
|
|
theMenu = mNoteTrackMenu;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (t->GetKind() == Track::Label){
|
|
|
|
theMenu = mLabelTrackMenu;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (theMenu) {
|
|
|
|
theMenu->Enable(OnMoveUpID, mTracks->CanMoveUp(t));
|
|
|
|
theMenu->Enable(OnMoveDownID, mTracks->CanMoveDown(t));
|
2013-11-03 01:17:58 +00:00
|
|
|
theMenu->Enable(OnMoveTopID, mTracks->CanMoveUp(t));
|
|
|
|
theMenu->Enable(OnMoveBottomID, mTracks->CanMoveDown(t));
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//We need to find the location of the menu rectangle.
|
|
|
|
wxRect r = FindTrackRect(t,true);
|
|
|
|
wxRect titleRect;
|
|
|
|
mTrackInfo.GetTitleBarRect(r,titleRect);
|
|
|
|
|
|
|
|
PopupMenu(theMenu, titleRect.x + 1,
|
|
|
|
titleRect.y + titleRect.height + 1);
|
|
|
|
}
|
|
|
|
|
2014-12-14 16:01:21 +00:00
|
|
|
mPopupMenuTarget = NULL;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
SetCapturedTrack(NULL);
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackMute(bool shiftDown, Track *t)
|
|
|
|
{
|
|
|
|
if (!t) {
|
|
|
|
t = GetFocusedTrack();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!t || (t->GetKind() != Track::Wave))
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
GetProject()->HandleTrackMute(t, shiftDown);
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
// Update mixer board, too.
|
2014-06-03 20:30:19 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard)
|
2010-07-21 04:53:38 +00:00
|
|
|
{
|
|
|
|
pMixerBoard->UpdateMute(); // Update for all tracks.
|
|
|
|
pMixerBoard->UpdateSolo(); // Update for all tracks.
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mAx->Updated();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackSolo(bool shiftDown, Track *t)
|
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!t)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
t = GetFocusedTrack();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!t || (t->GetKind() != Track::Wave))
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
GetProject()->HandleTrackSolo(t, shiftDown);
|
|
|
|
|
2010-07-21 04:53:38 +00:00
|
|
|
// Update mixer board, too.
|
2014-06-03 20:30:19 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard)
|
2010-07-21 04:53:38 +00:00
|
|
|
{
|
|
|
|
pMixerBoard->UpdateMute(); // Update for all tracks.
|
|
|
|
pMixerBoard->UpdateSolo(); // Update for all tracks.
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mAx->Updated();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTrackClose()
|
|
|
|
{
|
|
|
|
Track *t = GetFocusedTrack();
|
|
|
|
if(!t) return;
|
|
|
|
|
|
|
|
if( gAudioIO->IsStreamActive( GetProject()->GetAudioIOToken() ) )
|
|
|
|
{
|
|
|
|
mListener->TP_DisplayStatusMessage( _( "Can't delete track with active audio" ) );
|
|
|
|
wxBell();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveTrack( t );
|
|
|
|
|
|
|
|
SetCapturedTrack( NULL );
|
|
|
|
|
|
|
|
// BG: There are no more tracks on screen
|
|
|
|
if( mTracks->IsEmpty() )
|
|
|
|
{
|
|
|
|
//BG: Set zoom to normal
|
|
|
|
mViewInfo->zoom = 44100.0 / 512.0;
|
|
|
|
|
|
|
|
//STM: Set selection to 0,0
|
2014-10-05 17:10:09 +00:00
|
|
|
//PRL: and default the rest of the selection information
|
|
|
|
mViewInfo->selectedRegion = SelectedRegion();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mListener->TP_RedrawScrollbars();
|
|
|
|
mListener->TP_DisplayStatusMessage( wxT( "" ) ); //STM: Clear message if all tracks are removed
|
|
|
|
}
|
|
|
|
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Track * TrackPanel::GetFirstSelectedTrack()
|
|
|
|
{
|
|
|
|
|
|
|
|
TrackListIterator iter(mTracks);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
Track * t;
|
|
|
|
for ( t = iter.First();t!=NULL;t=iter.Next())
|
|
|
|
{
|
|
|
|
//Find the first selected track
|
|
|
|
if(t->GetSelected())
|
|
|
|
{
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
//if nothing is selected, return the first track
|
|
|
|
t = iter.First();
|
|
|
|
|
|
|
|
if(t)
|
|
|
|
return t;
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::EnsureVisible(Track * t)
|
|
|
|
{
|
|
|
|
TrackListIterator iter(mTracks);
|
|
|
|
Track *it = NULL;
|
|
|
|
Track *nt = NULL;
|
|
|
|
|
|
|
|
SetFocusedTrack(t);
|
|
|
|
|
|
|
|
int trackTop = 0;
|
|
|
|
int trackHeight =0;
|
|
|
|
|
|
|
|
for (it = iter.First(); it; it = iter.Next()) {
|
|
|
|
trackTop += trackHeight;
|
|
|
|
trackHeight = it->GetHeight();
|
|
|
|
|
|
|
|
//find the second track if this is stereo
|
|
|
|
if (it->GetLinked()) {
|
|
|
|
nt = iter.Next();
|
|
|
|
trackHeight += nt->GetHeight();
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
else if(MONO_WAVE_PAN(it)){
|
|
|
|
trackHeight += it->GetHeight(true);
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
else {
|
|
|
|
nt = it;
|
|
|
|
}
|
|
|
|
|
|
|
|
//We have found the track we want to ensure is visible.
|
|
|
|
if ((it == t) || (nt == t)) {
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get the size of the trackpanel.
|
|
|
|
int width, height;
|
|
|
|
GetSize(&width, &height);
|
|
|
|
|
|
|
|
if (trackTop < mViewInfo->vpos) {
|
|
|
|
height = mViewInfo->vpos - trackTop + mViewInfo->scrollStep;
|
|
|
|
height /= mViewInfo->scrollStep;
|
|
|
|
mListener->TP_ScrollUpDown(-height);
|
|
|
|
}
|
|
|
|
else if (trackTop + trackHeight > mViewInfo->vpos + height) {
|
|
|
|
height = (trackTop + trackHeight) - (mViewInfo->vpos + height);
|
|
|
|
height = (height + mViewInfo->scrollStep + 1) / mViewInfo->scrollStep;
|
|
|
|
mListener->TP_ScrollUpDown(height);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::DrawBordersAroundTrack(Track * t, wxDC * dc,
|
|
|
|
const wxRect r, const int vrul,
|
|
|
|
const int labelw)
|
|
|
|
{
|
|
|
|
// Border around track and label area
|
|
|
|
dc->SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
dc->SetPen(*wxBLACK_PEN);
|
|
|
|
dc->DrawRectangle(r.x, r.y, r.width - 1, r.height - 1);
|
|
|
|
|
|
|
|
AColor::Line(*dc, labelw, r.y, labelw, r.y + r.height - 1); // between vruler and TrackInfo
|
|
|
|
|
|
|
|
// The lines at bottom of 1st track and top of second track of stereo group
|
|
|
|
// Possibly replace with DrawRectangle to add left border.
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if (t->GetLinked() || MONO_WAVE_PAN(t)) {
|
|
|
|
int h1 = r.y + t->GetHeight() - kTopInset;
|
|
|
|
AColor::Line(*dc, vrul, h1 - 2, r.x + r.width - 1, h1 - 2);
|
|
|
|
AColor::Line(*dc, vrul, h1 + kTopInset, r.x + r.width - 1, h1 + kTopInset);
|
|
|
|
}
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t->GetLinked()) {
|
|
|
|
int h1 = r.y + t->GetHeight() - kTopInset;
|
|
|
|
AColor::Line(*dc, vrul, h1 - 2, r.x + r.width - 1, h1 - 2);
|
|
|
|
AColor::Line(*dc, vrul, h1 + kTopInset, r.x + r.width - 1, h1 + kTopInset);
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::DrawShadow(Track * /* t */ , wxDC * dc, const wxRect r)
|
|
|
|
{
|
|
|
|
int right = r.x + r.width - 1;
|
|
|
|
int bottom = r.y + r.height - 1;
|
|
|
|
|
|
|
|
// shadow
|
|
|
|
dc->SetPen(*wxBLACK_PEN);
|
|
|
|
|
|
|
|
// bottom
|
|
|
|
AColor::Line(*dc, r.x, bottom, right, bottom);
|
|
|
|
// right
|
|
|
|
AColor::Line(*dc, right, r.y, right, bottom);
|
|
|
|
|
|
|
|
// background
|
|
|
|
AColor::Dark(dc, false);
|
|
|
|
|
|
|
|
// bottom
|
|
|
|
AColor::Line(*dc, r.x, bottom, r.x + 1, bottom);
|
|
|
|
// right
|
2013-10-29 21:49:45 +00:00
|
|
|
AColor::Line(*dc, right, r.y, right, r.y + 1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the string to be displayed in the track label
|
2014-06-03 20:30:19 +00:00
|
|
|
/// indicating whether the track is mono, left, right, or
|
2010-01-23 19:44:49 +00:00
|
|
|
/// stereo and what sample rate it's using.
|
|
|
|
wxString TrackPanel::TrackSubText(Track * t)
|
|
|
|
{
|
|
|
|
wxString s = wxString::Format(wxT("%dHz"),
|
|
|
|
(int) (((WaveTrack *) t)->GetRate() +
|
|
|
|
0.5));
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if (t->GetLinked() && t->GetChannel() != Track::MonoChannel)
|
|
|
|
s = _("Stereo, ") + s;
|
|
|
|
#else
|
2010-01-23 19:44:49 +00:00
|
|
|
if (t->GetLinked())
|
|
|
|
s = _("Stereo, ") + s;
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
else {
|
|
|
|
if (t->GetChannel() == Track::MonoChannel)
|
|
|
|
s = _("Mono, ") + s;
|
|
|
|
else if (t->GetChannel() == Track::LeftChannel)
|
|
|
|
s = _("Left, ") + s;
|
|
|
|
else if (t->GetChannel() == Track::RightChannel)
|
|
|
|
s = _("Right, ") + s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handle the menu options that change a track between
|
|
|
|
/// left channel, right channel, and mono.
|
2013-09-21 19:30:00 +00:00
|
|
|
static int channels[] = { Track::LeftChannel, Track::RightChannel,
|
2010-01-23 19:44:49 +00:00
|
|
|
Track::MonoChannel
|
|
|
|
};
|
|
|
|
|
2013-09-21 19:30:00 +00:00
|
|
|
static const wxChar *channelmsgs[] = { _("Left Channel"), _("Right Channel"),
|
2010-01-23 19:44:49 +00:00
|
|
|
_("Mono")
|
|
|
|
};
|
|
|
|
|
|
|
|
void TrackPanel::OnChannelChange(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
int id = event.GetId();
|
|
|
|
wxASSERT(id >= OnChannelLeftID && id <= OnChannelMonoID);
|
|
|
|
wxASSERT(mPopupMenuTarget);
|
|
|
|
mPopupMenuTarget->SetChannel(channels[id - OnChannelLeftID]);
|
|
|
|
MakeParentPushState(wxString::Format(_("Changed '%s' to %s"),
|
|
|
|
mPopupMenuTarget->GetName().c_str(),
|
|
|
|
channelmsgs[id - OnChannelLeftID]),
|
|
|
|
_("Channel"));
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2013-11-03 01:17:58 +00:00
|
|
|
/// Swap the left and right channels of a stero track...
|
|
|
|
void TrackPanel::OnSwapChannels(wxCommandEvent & WXUNUSED(event))
|
|
|
|
{
|
|
|
|
Track *partner = mPopupMenuTarget->GetLink();
|
|
|
|
SplitStereo(true);
|
|
|
|
mPopupMenuTarget->SetChannel(Track::RightChannel);
|
|
|
|
partner->SetChannel(Track::LeftChannel);
|
|
|
|
|
|
|
|
(mTracks->MoveUp(partner));
|
|
|
|
partner->SetLinked(true);
|
|
|
|
|
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard) {
|
|
|
|
pMixerBoard->UpdateTrackClusters();
|
|
|
|
}
|
|
|
|
|
|
|
|
MakeParentPushState(wxString::Format(_("Swapped Channels in '%s'"),
|
|
|
|
mPopupMenuTarget->GetName().c_str()),
|
|
|
|
_("Swap Channels"));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2013-11-03 01:17:58 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// Split a stereo track into two tracks...
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnSplitStereo(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SplitStereo(true);
|
2013-10-30 22:10:11 +00:00
|
|
|
MakeParentPushState(wxString::Format(_("Split stereo track '%s'"),
|
|
|
|
mPopupMenuTarget->GetName().c_str()),
|
|
|
|
_("Split"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Split a stereo track into two mono tracks...
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnSplitStereoMono(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SplitStereo(false);
|
2013-10-30 22:10:11 +00:00
|
|
|
MakeParentPushState(wxString::Format(_("Split Stereo to Mono '%s'"),
|
|
|
|
mPopupMenuTarget->GetName().c_str()),
|
|
|
|
_("Split to Mono"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Split a stereo track into two tracks...
|
|
|
|
void TrackPanel::SplitStereo(bool stereo)
|
|
|
|
{
|
|
|
|
wxASSERT(mPopupMenuTarget);
|
|
|
|
|
|
|
|
if (!stereo)
|
|
|
|
mPopupMenuTarget->SetChannel(Track::MonoChannel);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
Track *partner = mPopupMenuTarget->GetLink();
|
2013-05-30 23:14:25 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(!stereo && MONO_WAVE_PAN(mPopupMenuTarget))
|
|
|
|
((WaveTrack*)mPopupMenuTarget)->SetVirtualState(true,true);
|
|
|
|
if(!stereo && MONO_WAVE_PAN(partner))
|
|
|
|
((WaveTrack*)partner)->SetVirtualState(true,true);
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (partner)
|
|
|
|
{
|
|
|
|
partner->SetName(mPopupMenuTarget->GetName());
|
|
|
|
if (!stereo)
|
|
|
|
partner->SetChannel(Track::MonoChannel); // Keep original stereo track name.
|
|
|
|
|
|
|
|
//On Demand - have each channel add it's own.
|
|
|
|
if (ODManager::IsInstanceCreated() && partner->GetKind() == Track::Wave)
|
|
|
|
ODManager::Instance()->MakeWaveTrackIndependent((WaveTrack*)partner);
|
|
|
|
}
|
|
|
|
|
|
|
|
mPopupMenuTarget->SetLinked(false);
|
2013-03-17 16:34:16 +00:00
|
|
|
//make sure neither track is smaller than its minimum height
|
|
|
|
if (mPopupMenuTarget->GetHeight() < mPopupMenuTarget->GetMinimizedHeight())
|
|
|
|
mPopupMenuTarget->SetHeight(mPopupMenuTarget->GetMinimizedHeight());
|
|
|
|
if (partner)
|
|
|
|
{
|
|
|
|
if (partner->GetHeight() < partner->GetMinimizedHeight())
|
|
|
|
partner->SetHeight(partner->GetMinimizedHeight());
|
2013-07-09 19:32:57 +00:00
|
|
|
|
2013-10-29 21:49:45 +00:00
|
|
|
// Make tracks the same height
|
|
|
|
if (mPopupMenuTarget->GetHeight() != partner->GetHeight())
|
|
|
|
{
|
|
|
|
mPopupMenuTarget->SetHeight(((mPopupMenuTarget->GetHeight())+(partner->GetHeight())) / 2.0);
|
|
|
|
partner->SetHeight(mPopupMenuTarget->GetHeight());
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Merge two tracks into one stereo track ??
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnMergeStereo(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxASSERT(mPopupMenuTarget);
|
|
|
|
mPopupMenuTarget->SetLinked(true);
|
|
|
|
Track *partner = mPopupMenuTarget->GetLink();
|
2013-05-30 23:14:25 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(MONO_WAVE_PAN(mPopupMenuTarget))
|
|
|
|
((WaveTrack*)mPopupMenuTarget)->SetVirtualState(false);
|
|
|
|
if(MONO_WAVE_PAN(partner))
|
|
|
|
((WaveTrack*)partner)->SetVirtualState(false);
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (partner) {
|
|
|
|
// Set partner's parameters to match target.
|
|
|
|
partner->Merge(*mPopupMenuTarget);
|
|
|
|
|
|
|
|
mPopupMenuTarget->SetChannel(Track::LeftChannel);
|
|
|
|
partner->SetChannel(Track::RightChannel);
|
2013-10-29 21:34:59 +00:00
|
|
|
|
|
|
|
// Set new track heights and minimized state
|
|
|
|
bool bBothMinimizedp=((mPopupMenuTarget->GetMinimized())&&(partner->GetMinimized()));
|
|
|
|
mPopupMenuTarget->SetMinimized(false);
|
|
|
|
partner->SetMinimized(false);
|
|
|
|
int AverageHeight=(mPopupMenuTarget->GetHeight() + partner->GetHeight())/ 2;
|
|
|
|
mPopupMenuTarget->SetHeight(AverageHeight);
|
|
|
|
partner->SetHeight(AverageHeight);
|
|
|
|
mPopupMenuTarget->SetMinimized(bBothMinimizedp);
|
|
|
|
partner->SetMinimized(bBothMinimizedp);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//On Demand - join the queues together.
|
|
|
|
if(ODManager::IsInstanceCreated() && partner->GetKind() == Track::Wave && mPopupMenuTarget->GetKind() == Track::Wave )
|
|
|
|
if(!ODManager::Instance()->MakeWaveTrackDependent((WaveTrack*)partner,(WaveTrack*)mPopupMenuTarget))
|
|
|
|
{
|
|
|
|
;
|
|
|
|
//TODO: in the future, we will have to check the return value of MakeWaveTrackDependent -
|
|
|
|
//if the tracks cannot merge, it returns false, and in that case we should not allow a merging.
|
|
|
|
//for example it returns false when there are two different types of ODTasks on each track's queue.
|
|
|
|
//we will need to display this to the user.
|
|
|
|
}
|
2013-10-29 21:34:59 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(wxString::Format(_("Made '%s' a stereo track"),
|
|
|
|
mPopupMenuTarget->GetName().
|
|
|
|
c_str()),
|
|
|
|
_("Make Stereo"));
|
|
|
|
} else
|
|
|
|
mPopupMenuTarget->SetLinked(false);
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the Display mode based on the menu choice in the Track Menu.
|
|
|
|
/// Note that gModes MUST BE IN THE SAME ORDER AS THE MENU CHOICES!!
|
|
|
|
/// const wxChar *gModes[] = { wxT("waveform"), wxT("waveformDB"),
|
|
|
|
/// wxT("spectrum"), wxT("pitch") };
|
|
|
|
void TrackPanel::OnSetDisplay(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
int id = event.GetId();
|
|
|
|
wxASSERT(id >= OnWaveformID && id <= OnPitchID);
|
|
|
|
wxASSERT(mPopupMenuTarget
|
|
|
|
&& mPopupMenuTarget->GetKind() == Track::Wave);
|
|
|
|
|
|
|
|
id -= OnWaveformID;
|
|
|
|
WaveTrack *wt = (WaveTrack *) mPopupMenuTarget;
|
|
|
|
if (wt->GetDisplay() != id) {
|
|
|
|
wt->SetDisplay(id);
|
|
|
|
mTrackArtist->InvalidateSpectrumCache(wt);
|
|
|
|
|
|
|
|
WaveTrack *l = (WaveTrack *) wt->GetLink();
|
|
|
|
if (l) {
|
|
|
|
l->SetDisplay(id);
|
|
|
|
mTrackArtist->InvalidateSpectrumCache(l);
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if (wt->GetDisplay() == WaveTrack::WaveformDisplay) {
|
|
|
|
wt->SetVirtualState(false);
|
|
|
|
}else if (id == WaveTrack::WaveformDisplay) {
|
|
|
|
wt->SetVirtualState(true);
|
|
|
|
}
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
UpdateVRuler(wt);
|
|
|
|
}
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the sample rate for a track, and if it is linked to
|
|
|
|
/// another track, that one as well.
|
|
|
|
void TrackPanel::SetRate(Track * pTrack, double rate)
|
|
|
|
{
|
|
|
|
((WaveTrack *) pTrack)->SetRate(rate);
|
|
|
|
Track *partner = mTracks->GetLink(pTrack);
|
|
|
|
if (partner)
|
|
|
|
((WaveTrack *) partner)->SetRate(rate);
|
2014-11-08 16:42:34 +00:00
|
|
|
// Separate conversion of "rate" enables changing the decimals without affecting i18n
|
|
|
|
wxString rateString = wxString::Format(wxT("%.3f"), rate);
|
|
|
|
MakeParentPushState(wxString::Format(_("Changed '%s' to %s Hz"),
|
|
|
|
pTrack->GetName().c_str(), rateString.c_str()),
|
2010-01-23 19:44:49 +00:00
|
|
|
_("Rate Change"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handles the selection from the Format submenu of the
|
|
|
|
/// track menu.
|
|
|
|
void TrackPanel::OnFormatChange(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
int id = event.GetId();
|
|
|
|
wxASSERT(id >= On16BitID && id <= OnFloatID);
|
|
|
|
wxASSERT(mPopupMenuTarget
|
|
|
|
&& mPopupMenuTarget->GetKind() == Track::Wave);
|
|
|
|
|
|
|
|
sampleFormat newFormat = int16Sample;
|
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
case On16BitID:
|
|
|
|
newFormat = int16Sample;
|
|
|
|
break;
|
|
|
|
case On24BitID:
|
|
|
|
newFormat = int24Sample;
|
|
|
|
break;
|
|
|
|
case OnFloatID:
|
|
|
|
newFormat = floatSample;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// ERROR -- should not happen
|
2011-11-16 05:59:48 +00:00
|
|
|
wxASSERT(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2011-12-02 06:46:31 +00:00
|
|
|
if (newFormat == ((WaveTrack*)mPopupMenuTarget)->GetSampleFormat())
|
|
|
|
return; // Nothing to do.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2011-11-18 03:47:43 +00:00
|
|
|
bool bResult = ((WaveTrack*)mPopupMenuTarget)->ConvertToSampleFormat(newFormat);
|
|
|
|
wxASSERT(bResult); // TO DO: Actually handle this.
|
2010-01-23 19:44:49 +00:00
|
|
|
Track *partner = mTracks->GetLink(mPopupMenuTarget);
|
|
|
|
if (partner)
|
2011-11-18 03:47:43 +00:00
|
|
|
{
|
|
|
|
bResult = ((WaveTrack*)partner)->ConvertToSampleFormat(newFormat);
|
|
|
|
wxASSERT(bResult); // TO DO: Actually handle this.
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
MakeParentPushState(wxString::Format(_("Changed '%s' to %s"),
|
|
|
|
mPopupMenuTarget->GetName().
|
|
|
|
c_str(),
|
|
|
|
GetSampleFormatStr(newFormat)),
|
|
|
|
_("Format Change"));
|
|
|
|
|
|
|
|
SetMenuCheck( *mFormatMenu, id );
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a format enumeration to a wxWidgets menu item Id.
|
|
|
|
int TrackPanel::IdOfFormat( int format )
|
|
|
|
{
|
|
|
|
switch (format) {
|
|
|
|
case int16Sample:
|
|
|
|
return On16BitID;
|
|
|
|
case int24Sample:
|
|
|
|
return On24BitID;
|
|
|
|
case floatSample:
|
|
|
|
return OnFloatID;
|
|
|
|
default:
|
|
|
|
// ERROR -- should not happen
|
|
|
|
wxASSERT( false );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return OnFloatID;// Compiler food.
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Puts a check mark at a given position in a menu, clearing all other check marks.
|
|
|
|
void TrackPanel::SetMenuCheck( wxMenu & menu, int newId )
|
|
|
|
{
|
|
|
|
wxMenuItemList & list = menu.GetMenuItems();
|
|
|
|
wxMenuItem * item;
|
|
|
|
int id;
|
|
|
|
|
|
|
|
for ( wxMenuItemList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext() )
|
|
|
|
{
|
|
|
|
item = node->GetData();
|
|
|
|
id = item->GetId();
|
|
|
|
menu.Check( id, id==newId );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-03 05:39:17 +00:00
|
|
|
const int nRates=12;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
/// gRates MUST CORRESPOND DIRECTLY TO THE RATES AS LISTED IN THE MENU!!
|
|
|
|
/// IN THE SAME ORDER!!
|
2013-09-21 19:30:00 +00:00
|
|
|
static int gRates[nRates] = { 8000, 11025, 16000, 22050, 44100, 48000, 88200, 96000,
|
2013-02-20 23:42:58 +00:00
|
|
|
176400, 192000, 352800, 384000 };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-12 05:11:43 +00:00
|
|
|
/// This method handles the selection from the Rate
|
2010-01-23 19:44:49 +00:00
|
|
|
/// submenu of the track menu, except for "Other" (/see OnRateOther).
|
|
|
|
void TrackPanel::OnRateChange(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
int id = event.GetId();
|
2012-12-03 05:39:17 +00:00
|
|
|
wxASSERT(id >= OnRate8ID && id <= OnRate384ID);
|
2010-01-23 19:44:49 +00:00
|
|
|
wxASSERT(mPopupMenuTarget
|
|
|
|
&& mPopupMenuTarget->GetKind() == Track::Wave);
|
|
|
|
|
|
|
|
SetMenuCheck( *mRateMenu, id );
|
|
|
|
SetRate(mPopupMenuTarget, gRates[id - OnRate8ID]);
|
|
|
|
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a sampling rate to a wxWidgets menu item id
|
|
|
|
int TrackPanel::IdOfRate( int rate )
|
|
|
|
{
|
|
|
|
for(int i=0;i<nRates;i++) {
|
2014-06-03 20:30:19 +00:00
|
|
|
if( gRates[i] == rate )
|
2010-01-23 19:44:49 +00:00
|
|
|
return i+OnRate8ID;
|
|
|
|
}
|
|
|
|
return OnRateOtherID;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnRateOther(wxCommandEvent &event)
|
|
|
|
{
|
|
|
|
wxASSERT(mPopupMenuTarget
|
|
|
|
&& mPopupMenuTarget->GetKind() == Track::Wave);
|
|
|
|
|
|
|
|
int newRate;
|
|
|
|
|
|
|
|
/// \todo Remove artificial constants!!
|
|
|
|
/// \todo Make a real dialog box out of this!!
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
wxDialog dlg(this, wxID_ANY, wxString(_("Set Rate")));
|
|
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
|
|
wxString rate;
|
|
|
|
wxArrayString rates;
|
|
|
|
wxComboBox *cb;
|
|
|
|
|
2014-11-08 16:42:34 +00:00
|
|
|
rate.Printf(wxT("%ld"), lrint(((WaveTrack *) mPopupMenuTarget)->GetRate()));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
rates.Add(wxT("8000"));
|
|
|
|
rates.Add(wxT("11025"));
|
|
|
|
rates.Add(wxT("16000"));
|
|
|
|
rates.Add(wxT("22050"));
|
|
|
|
rates.Add(wxT("44100"));
|
|
|
|
rates.Add(wxT("48000"));
|
2012-12-03 05:39:17 +00:00
|
|
|
rates.Add(wxT("88200"));
|
2010-01-23 19:44:49 +00:00
|
|
|
rates.Add(wxT("96000"));
|
2012-12-03 05:39:17 +00:00
|
|
|
rates.Add(wxT("176400"));
|
|
|
|
rates.Add(wxT("192000"));
|
|
|
|
rates.Add(wxT("352800"));
|
|
|
|
rates.Add(wxT("384000"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
S.StartVerticalLay(true);
|
|
|
|
{
|
2015-01-03 10:36:42 +00:00
|
|
|
S.SetBorder(10);
|
|
|
|
S.StartHorizontalLay(wxEXPAND, false);
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
cb = S.AddCombo(_("New sample rate (Hz):"),
|
|
|
|
rate,
|
|
|
|
&rates);
|
2013-09-24 23:24:22 +00:00
|
|
|
#if defined(__WXMAC__)
|
|
|
|
// As of wxMac-2.8.12, setting manually is required
|
|
|
|
// to handle rates not in the list. See: Bug #427
|
|
|
|
cb->SetValue(rate);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
S.EndHorizontalLay();
|
|
|
|
S.AddStandardButtons();
|
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
|
2015-01-03 10:36:42 +00:00
|
|
|
dlg.SetClientSize(dlg.GetSizer()->CalcMin());
|
2010-01-23 19:44:49 +00:00
|
|
|
dlg.Center();
|
|
|
|
|
|
|
|
if (dlg.ShowModal() != wxID_OK)
|
|
|
|
{
|
|
|
|
return; // user cancelled dialog
|
|
|
|
}
|
|
|
|
|
|
|
|
long lrate;
|
|
|
|
if (cb->GetValue().ToLong(&lrate) && lrate >= 1 && lrate <= 1000000)
|
|
|
|
{
|
|
|
|
newRate = (int)lrate;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxMessageBox(_("The entered value is invalid"), _("Error"),
|
|
|
|
wxICON_ERROR, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
SetMenuCheck( *mRateMenu, event.GetId() );
|
|
|
|
SetRate(mPopupMenuTarget, newRate);
|
|
|
|
|
|
|
|
MakeParentRedrawScrollbars();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnSetTimeTrackRange(wxCommandEvent & /*event*/)
|
|
|
|
{
|
|
|
|
TimeTrack *t = (TimeTrack*)mPopupMenuTarget;
|
|
|
|
|
|
|
|
if (t) {
|
2012-12-19 21:49:25 +00:00
|
|
|
long lower = (long) (t->GetRangeLower() * 100.0 + 0.5);
|
|
|
|
long upper = (long) (t->GetRangeUpper() * 100.0 + 0.5);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2012-12-29 16:28:28 +00:00
|
|
|
// MB: these lower/upper limits match the maximum allowed range of the time track
|
|
|
|
// envelope, but this is not strictly required
|
2010-01-23 19:44:49 +00:00
|
|
|
lower = wxGetNumberFromUser(_("Change lower speed limit (%) to:"),
|
|
|
|
_("Lower speed limit"),
|
|
|
|
_("Lower speed limit"),
|
|
|
|
lower,
|
2012-12-29 16:28:28 +00:00
|
|
|
10,
|
|
|
|
1000);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
upper = wxGetNumberFromUser(_("Change upper speed limit (%) to:"),
|
|
|
|
_("Upper speed limit"),
|
|
|
|
_("Upper speed limit"),
|
|
|
|
upper,
|
|
|
|
lower+1,
|
2012-12-29 16:28:28 +00:00
|
|
|
1000);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2012-12-29 16:28:28 +00:00
|
|
|
if( lower >= 10 && upper <= 1000 && lower < upper ) {
|
2012-12-19 21:49:25 +00:00
|
|
|
t->SetRangeLower((double)lower / 100.0);
|
|
|
|
t->SetRangeUpper((double)upper / 100.0);
|
2014-11-08 16:42:34 +00:00
|
|
|
MakeParentPushState(wxString::Format(_("Set range to '%ld' - '%ld'"),
|
|
|
|
lower,
|
|
|
|
upper),
|
2012-03-20 16:17:37 +00:00
|
|
|
/* i18n-hint: (verb)*/
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
_("Set Range"));
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
void TrackPanel::OnTimeTrackLin(wxCommandEvent & /*event*/)
|
|
|
|
{
|
|
|
|
TimeTrack *t = (TimeTrack*)mPopupMenuTarget;
|
|
|
|
t->SetDisplayLog(false);
|
|
|
|
UpdateVRuler(t);
|
|
|
|
MakeParentPushState(_("Set time track display to linear"), _("Set Display"));
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTimeTrackLog(wxCommandEvent & /*event*/)
|
|
|
|
{
|
|
|
|
TimeTrack *t = (TimeTrack*)mPopupMenuTarget;
|
|
|
|
t->SetDisplayLog(true);
|
|
|
|
UpdateVRuler(t);
|
|
|
|
MakeParentPushState(_("Set time track display to logarithmic"), _("Set Display"));
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::OnTimeTrackLogInt(wxCommandEvent & /*event*/)
|
|
|
|
{
|
|
|
|
TimeTrack *t = (TimeTrack*)mPopupMenuTarget;
|
|
|
|
if(t->GetInterpolateLog()) {
|
|
|
|
t->SetInterpolateLog(false);
|
|
|
|
MakeParentPushState(_("Set time track interpolation to linear"), _("Set Interpolation"));
|
|
|
|
} else {
|
|
|
|
t->SetInterpolateLog(true);
|
|
|
|
MakeParentPushState(_("Set time track interpolation to logarithmic"), _("Set Interpolation"));
|
|
|
|
}
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2013-11-03 01:17:58 +00:00
|
|
|
/// Move a track up, down, to top or to bottom.
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackPanel::OnMoveTrack(wxCommandEvent & event)
|
|
|
|
{
|
2013-11-03 01:17:58 +00:00
|
|
|
wxASSERT(event.GetId() == OnMoveUpID || event.GetId() == OnMoveDownID ||
|
|
|
|
event.GetId() == OnMoveTopID || event.GetId() == OnMoveBottomID);
|
|
|
|
|
|
|
|
wxString direction;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2013-11-03 01:17:58 +00:00
|
|
|
switch (event.GetId())
|
|
|
|
{
|
|
|
|
case OnMoveTopID :
|
|
|
|
/* i18n-hint: where the track is moving to.*/
|
|
|
|
direction = _("to Top");
|
|
|
|
|
|
|
|
while (mTracks->CanMoveUp(mPopupMenuTarget)) {
|
|
|
|
if (mTracks->Move(mPopupMenuTarget, true)) {
|
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board.
|
|
|
|
if (pMixerBoard && (mPopupMenuTarget->GetKind() == Track::Wave))
|
|
|
|
pMixerBoard->MoveTrackCluster((WaveTrack*)mPopupMenuTarget, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case OnMoveBottomID :
|
|
|
|
/* i18n-hint: where the track is moving to.*/
|
|
|
|
direction = _("to Bottom");
|
|
|
|
|
|
|
|
while (mTracks->CanMoveDown(mPopupMenuTarget)) {
|
|
|
|
if (mTracks->Move(mPopupMenuTarget, false)) {
|
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board.
|
|
|
|
if (pMixerBoard && (mPopupMenuTarget->GetKind() == Track::Wave))
|
|
|
|
pMixerBoard->MoveTrackCluster((WaveTrack*)mPopupMenuTarget, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
bool bUp = (OnMoveUpID == event.GetId());
|
|
|
|
/* i18n-hint: a direction.*/
|
|
|
|
direction = bUp ? _("Up") : _("Down");
|
|
|
|
|
|
|
|
if (mTracks->Move(mPopupMenuTarget, bUp)) {
|
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard && (mPopupMenuTarget->GetKind() == Track::Wave)) {
|
|
|
|
pMixerBoard->MoveTrackCluster((WaveTrack*)mPopupMenuTarget, bUp);
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-11-03 01:17:58 +00:00
|
|
|
|
|
|
|
/* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
|
|
|
|
wxString longDesc = (_("Moved"));
|
|
|
|
/* i18n-hint: The direction of movement will be up, down, to top or to bottom.. */
|
|
|
|
wxString shortDesc = (_("Move Track"));
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
longDesc = (wxString::Format(wxT("%s '%s' %s"), longDesc.c_str(),
|
2013-11-03 01:17:58 +00:00
|
|
|
mPopupMenuTarget->GetName().c_str(), direction.c_str()));
|
|
|
|
shortDesc = (wxString::Format(wxT("%s %s"), shortDesc.c_str(), direction.c_str()));
|
|
|
|
|
|
|
|
MakeParentPushState(longDesc, shortDesc);
|
|
|
|
|
|
|
|
Refresh(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// This only applies to MIDI tracks. Presumably, it shifts the
|
|
|
|
/// whole sequence by an octave.
|
|
|
|
void TrackPanel::OnChangeOctave(wxCommandEvent & event)
|
|
|
|
{
|
|
|
|
#if defined(USE_MIDI)
|
|
|
|
wxASSERT(event.GetId() == OnUpOctaveID
|
|
|
|
|| event.GetId() == OnDownOctaveID);
|
|
|
|
wxASSERT(mPopupMenuTarget->GetKind() == Track::Note);
|
|
|
|
NoteTrack *t = (NoteTrack *) mPopupMenuTarget;
|
|
|
|
|
|
|
|
bool bDown = (OnDownOctaveID == event.GetId());
|
|
|
|
t->SetBottomNote(t->GetBottomNote() + ((bDown) ? -12 : 12));
|
|
|
|
|
2013-12-30 00:41:18 +00:00
|
|
|
MakeParentModifyState(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnSetName(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
Track *t = mPopupMenuTarget;
|
2014-06-03 20:30:19 +00:00
|
|
|
if (t)
|
2010-07-30 22:03:59 +00:00
|
|
|
{
|
|
|
|
wxString oldName = t->GetName();
|
2014-06-03 20:30:19 +00:00
|
|
|
wxString newName =
|
2010-07-30 22:03:59 +00:00
|
|
|
wxGetTextFromUser(_("Change track name to:"),
|
|
|
|
_("Track Name"), oldName);
|
|
|
|
if (newName != wxT("")) // wxGetTextFromUser returns empty string on Cancel.
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
t->SetName(newName);
|
2014-06-03 20:30:19 +00:00
|
|
|
// if we have a linked channel this name should change as well
|
2010-07-30 22:03:59 +00:00
|
|
|
// (otherwise sort by name and time will crash).
|
|
|
|
if (t->GetLinked())
|
2010-05-04 03:14:43 +00:00
|
|
|
t->GetLink()->SetName(newName);
|
2010-07-21 04:53:38 +00:00
|
|
|
|
2010-07-30 22:03:59 +00:00
|
|
|
MixerBoard* pMixerBoard = this->GetMixerBoard();
|
|
|
|
if (pMixerBoard && (t->GetKind() == Track::Wave))
|
|
|
|
pMixerBoard->UpdateName((WaveTrack*)t);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-07-30 22:03:59 +00:00
|
|
|
MakeParentPushState(wxString::Format(_("Renamed '%s' to '%s'"),
|
|
|
|
oldName.c_str(),
|
|
|
|
newName.c_str()),
|
|
|
|
_("Name Change"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-07-30 22:03:59 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cut selected text if cut menu item is selected
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnCutSelectedText(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
LabelTrack *lt = (LabelTrack *)mPopupMenuTarget;
|
|
|
|
if (lt->CutSelectedText()) {
|
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
2011-05-22 13:41:01 +00:00
|
|
|
PUSH_CONSOLIDATE);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
RefreshTrack(lt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Copy selected text if copy menu item is selected
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnCopySelectedText(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
LabelTrack *lt = (LabelTrack *)mPopupMenuTarget;
|
|
|
|
lt->CopySelectedText();
|
|
|
|
RefreshTrack(lt);
|
|
|
|
}
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
/// paste selected text if paste menu item is selected
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnPasteSelectedText(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
LabelTrack *lt = (LabelTrack *)mPopupMenuTarget;
|
2014-10-05 17:10:09 +00:00
|
|
|
if (lt->PasteSelectedText(mViewInfo->selectedRegion.t0(),
|
|
|
|
mViewInfo->selectedRegion.t1())) {
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeParentPushState(_("Modified Label"),
|
|
|
|
_("Label Edit"),
|
|
|
|
true /* consolidate */);
|
|
|
|
}
|
|
|
|
RefreshTrack(lt);
|
|
|
|
}
|
|
|
|
|
2014-07-12 14:26:07 +00:00
|
|
|
/// delete selected label
|
|
|
|
void TrackPanel::OnDeleteSelectedLabel(wxCommandEvent & WXUNUSED(event))
|
|
|
|
{
|
|
|
|
LabelTrack *lt = (LabelTrack *)mPopupMenuTarget;
|
2014-10-05 17:10:09 +00:00
|
|
|
int ndx = lt->GetLabelIndex(mViewInfo->selectedRegion.t0(),
|
|
|
|
mViewInfo->selectedRegion.t1());
|
2014-07-12 14:26:07 +00:00
|
|
|
if (ndx != -1) {
|
|
|
|
lt->DeleteLabel(ndx);
|
|
|
|
MakeParentPushState(_("Deleted Label"),
|
|
|
|
_("Label Edit"),
|
|
|
|
true /* consolidate */);
|
|
|
|
}
|
|
|
|
RefreshTrack(lt);
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Small helper class to enumerate all fonts in the system
|
|
|
|
// We use this because the default implementation of
|
|
|
|
// wxFontEnumerator::GetFacenames() has changed between wx2.6 and 2.8
|
|
|
|
class TrackPanelFontEnumerator : public wxFontEnumerator
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TrackPanelFontEnumerator(wxArrayString* fontNames) :
|
|
|
|
mFontNames(fontNames) {}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
virtual bool OnFacename(const wxString& font)
|
|
|
|
{
|
|
|
|
mFontNames->Add(font);
|
|
|
|
return true;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
private:
|
|
|
|
wxArrayString* mFontNames;
|
|
|
|
};
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnSetFont(wxCommandEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxArrayString facenames;
|
|
|
|
TrackPanelFontEnumerator fontEnumerator(&facenames);
|
|
|
|
fontEnumerator.EnumerateFacenames(wxFONTENCODING_SYSTEM, false);
|
|
|
|
|
|
|
|
wxString facename = gPrefs->Read(wxT("/GUI/LabelFontFacename"), wxT(""));
|
|
|
|
long fontsize = gPrefs->Read(wxT("/GUI/LabelFontSize"), 12);
|
|
|
|
|
2012-03-20 16:17:37 +00:00
|
|
|
/* i18n-hint: (noun) This is the font for the label track.*/
|
2010-01-23 19:44:49 +00:00
|
|
|
wxDialog dlg(this, wxID_ANY, wxString(_("Label Track Font")));
|
|
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
|
|
wxListBox *lb;
|
|
|
|
wxSpinCtrl *sc;
|
|
|
|
|
|
|
|
S.StartVerticalLay(true);
|
|
|
|
{
|
|
|
|
S.StartMultiColumn(2, wxEXPAND);
|
|
|
|
{
|
|
|
|
S.SetStretchyRow(0);
|
|
|
|
S.SetStretchyCol(1);
|
|
|
|
|
2012-03-20 16:17:37 +00:00
|
|
|
/* i18n-hint: (noun) The name of the typeface*/
|
2010-01-23 19:44:49 +00:00
|
|
|
S.AddPrompt(_("Face name"));
|
|
|
|
lb = new wxListBox(&dlg, wxID_ANY,
|
|
|
|
wxDefaultPosition,
|
|
|
|
wxDefaultSize,
|
|
|
|
facenames,
|
|
|
|
wxLB_SINGLE);
|
2012-03-20 16:17:37 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
lb->SetName(_("Face name"));
|
|
|
|
lb->SetSelection(facenames.Index(facename));
|
|
|
|
S.AddWindow(lb, wxALIGN_LEFT | wxEXPAND | wxALL);
|
|
|
|
|
2012-03-20 16:17:37 +00:00
|
|
|
/* i18n-hint: (noun) The size of the typeface*/
|
2010-01-23 19:44:49 +00:00
|
|
|
S.AddPrompt(_("Face size"));
|
|
|
|
sc = new wxSpinCtrl(&dlg, wxID_ANY,
|
2014-11-08 16:42:34 +00:00
|
|
|
wxString::Format(wxT("%ld"), fontsize),
|
2010-01-23 19:44:49 +00:00
|
|
|
wxDefaultPosition,
|
|
|
|
wxDefaultSize,
|
|
|
|
wxSP_ARROW_KEYS,
|
|
|
|
8, 48, fontsize);
|
|
|
|
sc->SetName(_("Face size"));
|
|
|
|
S.AddWindow(sc, wxALIGN_LEFT | wxALL);
|
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
S.AddStandardButtons();
|
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
|
|
|
|
dlg.Layout();
|
|
|
|
dlg.Fit();
|
|
|
|
dlg.CenterOnParent();
|
|
|
|
if (dlg.ShowModal() == wxID_CANCEL) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
gPrefs->Write(wxT("/GUI/LabelFontFacename"), lb->GetStringSelection());
|
|
|
|
gPrefs->Write(wxT("/GUI/LabelFontSize"), sc->GetValue());
|
2012-08-02 06:03:19 +00:00
|
|
|
gPrefs->Flush();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
LabelTrack::ResetFont();
|
|
|
|
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
/// Determines which track is under the mouse
|
2010-01-23 19:44:49 +00:00
|
|
|
/// @param mouseX - mouse X position.
|
|
|
|
/// @param mouseY - mouse Y position.
|
|
|
|
/// @param label - true iff the X Y position is relative to side-panel with the labels in it.
|
|
|
|
/// @param link - true iff we should consider a hit in any linked track as a hit.
|
|
|
|
/// @param *trackRect - returns track rectangle.
|
|
|
|
Track *TrackPanel::FindTrack(int mouseX, int mouseY, bool label, bool link,
|
|
|
|
wxRect * trackRect)
|
|
|
|
{
|
|
|
|
wxRect r;
|
|
|
|
r.x = 0;
|
|
|
|
r.y = -mViewInfo->vpos;
|
|
|
|
r.y += kTopInset;
|
|
|
|
GetSize(&r.width, &r.height);
|
|
|
|
|
|
|
|
if (label) {
|
|
|
|
r.width = GetLeftOffset();
|
|
|
|
} else {
|
|
|
|
r.x = GetLeftOffset();
|
|
|
|
r.width -= GetLeftOffset();
|
|
|
|
}
|
|
|
|
|
|
|
|
VisibleTrackIterator iter(GetProject());
|
|
|
|
for (Track * t = iter.First(); t; t = iter.Next()) {
|
|
|
|
r.y = t->GetY() - mViewInfo->vpos + kTopInset;
|
|
|
|
r.height = t->GetHeight();
|
|
|
|
|
|
|
|
if (link && t->GetLink()) {
|
|
|
|
Track *l = t->GetLink();
|
|
|
|
int h = l->GetHeight();
|
|
|
|
if (!t->GetLinked()) {
|
|
|
|
t = l;
|
2013-05-30 23:14:25 +00:00
|
|
|
r.y = t->GetY() - mViewInfo->vpos + kTopInset;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
r.height += h;
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
else if(link && MONO_WAVE_PAN(t))
|
|
|
|
{
|
|
|
|
r.height += t->GetHeight(true);
|
2013-10-29 21:49:45 +00:00
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#endif
|
2014-06-03 20:30:19 +00:00
|
|
|
//Determine whether the mouse is inside
|
2010-01-23 19:44:49 +00:00
|
|
|
//the current rectangle. If so, recalculate
|
|
|
|
//the proper dimensions and return.
|
|
|
|
if (r.Contains(mouseX, mouseY)) {
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
t->SetVirtualStereo(false);
|
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
if (trackRect) {
|
|
|
|
r.y -= kTopInset;
|
|
|
|
if (label) {
|
|
|
|
r.x += kLeftInset;
|
|
|
|
r.width -= kLeftInset;
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.height -= kTopInset;
|
|
|
|
}
|
|
|
|
*trackRect = r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
2013-05-30 23:14:25 +00:00
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
if(!link && MONO_WAVE_PAN(t)){
|
|
|
|
r.y = t->GetY(true) - mViewInfo->vpos + kTopInset;
|
|
|
|
r.height = t->GetHeight(true);
|
|
|
|
if (r.Contains(mouseX, mouseY)) {
|
|
|
|
t->SetVirtualStereo(true);
|
|
|
|
if (trackRect) {
|
|
|
|
r.y -= kTopInset;
|
|
|
|
if (label) {
|
|
|
|
r.x += kLeftInset;
|
|
|
|
r.width -= kLeftInset;
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.height -= kTopInset;
|
|
|
|
}
|
|
|
|
*trackRect = r;
|
|
|
|
}
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // EXPERIMENTAL_OUTPUT_DISPLAY
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This finds the rectangle of a given track, either the
|
|
|
|
/// of the label 'adornment' or the track itself
|
|
|
|
wxRect TrackPanel::FindTrackRect(Track * target, bool label)
|
|
|
|
{
|
|
|
|
if (!target) {
|
|
|
|
return wxRect(0,0,0,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
wxRect r(0,
|
|
|
|
target->GetY() - mViewInfo->vpos,
|
|
|
|
GetSize().GetWidth(),
|
|
|
|
target->GetHeight());
|
|
|
|
|
|
|
|
// The check for a null linked track is necessary because there's
|
|
|
|
// a possible race condition between the time the 2 linked tracks
|
2010-09-12 05:11:43 +00:00
|
|
|
// are added and when wxAccessible methods are called. This is
|
2010-01-23 19:44:49 +00:00
|
|
|
// most evident when using Jaws.
|
|
|
|
if (target->GetLinked() && target->GetLink()) {
|
|
|
|
r.height += target->GetLink()->GetHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (label) {
|
|
|
|
r.x += kLeftInset;
|
|
|
|
r.width -= kLeftInset;
|
|
|
|
r.y += kTopInset;
|
|
|
|
r.height -= kTopInset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
int TrackPanel::GetVRulerWidth() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
return vrulerSize.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Displays the bounds of the selection in the status bar.
|
|
|
|
void TrackPanel::DisplaySelection()
|
|
|
|
{
|
|
|
|
if (!mListener)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// DM: Note that the Selection Bar can actually MODIFY the selection
|
|
|
|
// if snap-to mode is on!!!
|
|
|
|
mListener->TP_DisplaySelection();
|
|
|
|
}
|
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
bool TrackPanel::MoveClipToTrack(WaveClip *clip, WaveTrack* dst)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2011-04-26 13:15:19 +00:00
|
|
|
WaveTrack *src = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveClip *clip2 = NULL;
|
|
|
|
WaveTrack *src2 = NULL;
|
|
|
|
WaveTrack *dst2 = NULL;
|
2011-04-26 21:49:30 +00:00
|
|
|
size_t i;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef USE_MIDI
|
|
|
|
// dst could be a note track. Can't move clip to a note track.
|
2011-11-16 05:59:48 +00:00
|
|
|
// EXPLAIN: How could dst be a note track (pointer)? It's declared to be a WaveTrack*. I think this test is pointless.
|
2010-09-18 21:02:36 +00:00
|
|
|
if (dst->GetKind() != Track::Wave) return false;
|
|
|
|
#endif
|
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
for (i = 0; i < mCapturedClipArray.GetCount(); i++) {
|
|
|
|
if (clip == mCapturedClipArray[i].clip) {
|
|
|
|
src = (WaveTrack*)mCapturedClipArray[i].track;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
if (!src)
|
|
|
|
return false;
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Make sure we have the first track of two stereo tracks
|
|
|
|
// with both source and destination
|
|
|
|
if (!src->GetLinked() && mTracks->GetLink(src)) {
|
2011-04-28 18:06:49 +00:00
|
|
|
// set it to NULL in case there is no L channel clip
|
|
|
|
clip = NULL;
|
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
// find the first track by getting the linked track from src
|
|
|
|
// assumes that mCapturedArray[i].clip and .track is not NULL.
|
|
|
|
for (i = 0; i < mCapturedClipArray.GetCount(); i++) {
|
|
|
|
if (mTracks->GetLink(src) == mCapturedClipArray[i].track) {
|
|
|
|
clip = mCapturedClipArray[i].clip;
|
|
|
|
break;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2011-04-26 13:15:19 +00:00
|
|
|
|
|
|
|
src = (WaveTrack*)mTracks->GetLink(src);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
if (!dst->GetLinked() && mTracks->GetLink(dst))
|
|
|
|
dst = (WaveTrack*)mTracks->GetLink(dst);
|
|
|
|
|
|
|
|
// Get the second track of two stereo tracks
|
|
|
|
src2 = (WaveTrack*)mTracks->GetLink(src);
|
|
|
|
dst2 = (WaveTrack*)mTracks->GetLink(dst);
|
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
for (i = 0; i < mCapturedClipArray.GetCount(); i++) {
|
|
|
|
if (mCapturedClipArray[i].track == src2) {
|
|
|
|
clip2 = mCapturedClipArray[i].clip;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if ((src2 && !dst2) || (dst2 && !src2))
|
|
|
|
return false; // cannot move stereo- to mono track or other way around
|
|
|
|
|
2011-04-28 18:06:49 +00:00
|
|
|
// if only the right clip of a stereo pair is being dragged, use clip instead of clip2 to get mono behavior.
|
|
|
|
if (!clip && clip2) {
|
|
|
|
clip = clip2;
|
|
|
|
src = src2;
|
|
|
|
dst = dst2;
|
|
|
|
clip2 = NULL;
|
|
|
|
src2 = dst2 = NULL;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!dst->CanInsertClip(clip))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (clip2) {
|
2011-04-18 18:54:18 +00:00
|
|
|
// we should have a source and dest track
|
|
|
|
if (!dst2 || !src2)
|
|
|
|
return false;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!dst2->CanInsertClip(clip2))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
src->MoveClipToTrack(clip, dst);
|
|
|
|
if (src2)
|
|
|
|
src2->MoveClipToTrack(clip2, dst2);
|
|
|
|
|
2011-04-26 13:15:19 +00:00
|
|
|
// update the captured clip array.
|
|
|
|
for (i = 0; i < mCapturedClipArray.GetCount(); i++) {
|
2011-04-29 13:27:37 +00:00
|
|
|
if (clip && mCapturedClipArray[i].clip == clip) {
|
2011-04-26 13:15:19 +00:00
|
|
|
mCapturedClipArray[i].track = dst;
|
2011-04-29 13:27:37 +00:00
|
|
|
} else if (clip2 && mCapturedClipArray[i].clip == clip2) {
|
2011-04-26 13:15:19 +00:00
|
|
|
mCapturedClipArray[i].track = dst2;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Track *TrackPanel::GetFocusedTrack()
|
|
|
|
{
|
|
|
|
return mAx->GetFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackPanel::SetFocusedTrack( Track *t )
|
|
|
|
{
|
|
|
|
// Make sure we always have the first linked track of a stereo track
|
|
|
|
if (t && !t->GetLinked() && t->GetLink())
|
|
|
|
t = (WaveTrack*)t->GetLink();
|
|
|
|
|
|
|
|
AudacityProject *p = GetActiveProject();
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (p && p->HasKeyboardCapture()) {
|
|
|
|
wxCommandEvent e(EVT_RELEASE_KEYBOARD);
|
|
|
|
e.SetEventObject(this);
|
|
|
|
GetParent()->GetEventHandler()->ProcessEvent(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t && t->GetKind() == Track::Label) {
|
|
|
|
wxCommandEvent e(EVT_CAPTURE_KEYBOARD);
|
|
|
|
e.SetEventObject(this);
|
|
|
|
GetParent()->GetEventHandler()->ProcessEvent(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
mAx->SetFocus( t );
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnSetFocus(wxFocusEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SetFocusedTrack( GetFocusedTrack() );
|
|
|
|
Refresh( false );
|
|
|
|
}
|
|
|
|
|
2013-08-25 21:51:26 +00:00
|
|
|
void TrackPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
Refresh( false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
TrackInfo code is destined to move out of this file.
|
2014-06-03 20:30:19 +00:00
|
|
|
Code should become a lot cleaner when we have sizers.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
TrackInfo::TrackInfo(wxWindow * pParentIn)
|
|
|
|
{
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
pParent = pParentIn;
|
2010-04-21 05:03:24 +00:00
|
|
|
|
|
|
|
// Populate sliders array
|
|
|
|
for (unsigned int i = 0; i < kInitialSliders; ++i) {
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeMoreSliders();
|
|
|
|
}
|
2010-04-21 05:03:24 +00:00
|
|
|
mSliderOffset = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
int fontSize = 10;
|
2014-10-06 08:10:50 +00:00
|
|
|
mFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
int allowableWidth = GetTrackInfoWidth() - 2; // 2 to allow for left/right borders
|
2010-01-23 19:44:49 +00:00
|
|
|
int textWidth, textHeight;
|
|
|
|
do {
|
|
|
|
mFont.SetPointSize(fontSize);
|
|
|
|
pParent->GetTextExtent(_("Stereo, 999999Hz"),
|
|
|
|
&textWidth,
|
|
|
|
&textHeight,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
&mFont);
|
|
|
|
fontSize--;
|
|
|
|
} while (textWidth >= allowableWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
TrackInfo::~TrackInfo()
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
for(i=0; i<mGains.Count(); i++)
|
|
|
|
delete mGains[i];
|
|
|
|
for(i=0; i<mPans.Count(); i++)
|
|
|
|
delete mPans[i];
|
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
static const int kTrackInfoWidth = 100;
|
2010-09-16 23:08:33 +00:00
|
|
|
static const int kTrackInfoBtnSize = 16; // widely used dimension, usually height
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
int TrackInfo::GetTrackInfoWidth() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
return kTrackInfoWidth;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetCloseBoxRect(const wxRect r, wxRect & dest) const
|
|
|
|
{
|
|
|
|
dest.x = r.x;
|
|
|
|
dest.y = r.y;
|
2014-06-03 20:30:19 +00:00
|
|
|
dest.width = kTrackInfoBtnSize;
|
2010-09-16 23:08:33 +00:00
|
|
|
dest.height = kTrackInfoBtnSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetTitleBarRect(const wxRect r, wxRect & dest) const
|
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
dest.x = r.x + kTrackInfoBtnSize; // to right of CloseBoxRect
|
2010-01-23 19:44:49 +00:00
|
|
|
dest.y = r.y;
|
2010-09-16 23:08:33 +00:00
|
|
|
dest.width = kTrackInfoWidth - r.x - kTrackInfoBtnSize; // to right of CloseBoxRect
|
|
|
|
dest.height = kTrackInfoBtnSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetMuteSoloRect(const wxRect r, wxRect & dest, bool solo, bool bHasSoloButton) const
|
|
|
|
{
|
|
|
|
dest.x = r.x ;
|
|
|
|
dest.y = r.y + 50;
|
|
|
|
dest.width = 48;
|
2010-09-16 23:08:33 +00:00
|
|
|
dest.height = kTrackInfoBtnSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if( !bHasSoloButton )
|
|
|
|
{
|
|
|
|
dest.width +=48;
|
|
|
|
}
|
|
|
|
else if (solo)
|
|
|
|
{
|
|
|
|
dest.x += 48;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetGainRect(const wxRect r, wxRect & dest) const
|
|
|
|
{
|
|
|
|
dest.x = r.x + 7;
|
|
|
|
dest.y = r.y + 70;
|
|
|
|
dest.width = 84;
|
|
|
|
dest.height = 25;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetPanRect(const wxRect r, wxRect & dest) const
|
|
|
|
{
|
|
|
|
dest.x = r.x + 7;
|
|
|
|
dest.y = r.y + 100;
|
|
|
|
dest.width = 84;
|
|
|
|
dest.height = 25;
|
|
|
|
}
|
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
void TrackInfo::GetMinimizeRect(const wxRect r, wxRect &dest) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
const int kBlankWidth = kTrackInfoBtnSize + 4;
|
|
|
|
dest.x = r.x + kBlankWidth;
|
|
|
|
dest.y = r.y + r.height - 19;
|
|
|
|
// Width is kTrackInfoWidth less space on left for track select and on right for sync-lock icon.
|
|
|
|
dest.width = kTrackInfoWidth - (2 * kBlankWidth);
|
2014-06-03 20:30:19 +00:00
|
|
|
dest.height = kTrackInfoBtnSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
void TrackInfo::GetSyncLockIconRect(const wxRect r, wxRect &dest) const
|
|
|
|
{
|
|
|
|
dest.x = r.x + kTrackInfoWidth - kTrackInfoBtnSize - 4; // to right of minimize button
|
|
|
|
dest.y = r.y + r.height - 19;
|
|
|
|
dest.width = kTrackInfoBtnSize;
|
|
|
|
dest.height = kTrackInfoBtnSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// \todo Probably should move to 'Utils.cpp'.
|
|
|
|
void TrackInfo::SetTrackInfoFont(wxDC * dc)
|
|
|
|
{
|
|
|
|
dc->SetFont(mFont);
|
|
|
|
}
|
|
|
|
|
2010-09-14 05:52:01 +00:00
|
|
|
void TrackInfo::DrawBordersWithin(wxDC* dc, const wxRect r, bool bHasMuteSolo)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
AColor::Dark(dc, false); // same color as border of toolbars (ToolBar::OnPaint())
|
2010-09-14 05:52:01 +00:00
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
// below close box and title bar
|
2014-06-03 20:30:19 +00:00
|
|
|
AColor::Line(*dc, r.x, r.y + kTrackInfoBtnSize, kTrackInfoWidth, r.y + kTrackInfoBtnSize);
|
2010-09-16 23:08:33 +00:00
|
|
|
|
|
|
|
// between close box and title bar
|
2014-06-03 20:30:19 +00:00
|
|
|
AColor::Line(*dc, r.x + kTrackInfoBtnSize, r.y, r.x + kTrackInfoBtnSize, r.y + kTrackInfoBtnSize);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if( bHasMuteSolo && (r.height > (66+18) ))
|
|
|
|
{
|
2010-09-16 23:08:33 +00:00
|
|
|
AColor::Line(*dc, r.x, r.y + 50, kTrackInfoWidth, r.y + 50); // above mute/solo
|
|
|
|
AColor::Line(*dc, r.x + 48 , r.y + 50, r.x + 48, r.y + 66); // between mute/solo
|
|
|
|
AColor::Line(*dc, r.x, r.y + 66, kTrackInfoWidth, r.y + 66); // below mute/solo
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
// left of and above minimize button
|
|
|
|
wxRect minimizeRect;
|
|
|
|
this->GetMinimizeRect(r, minimizeRect);
|
2014-06-03 20:30:19 +00:00
|
|
|
AColor::Line(*dc, minimizeRect.x - 1, minimizeRect.y,
|
|
|
|
minimizeRect.x - 1, minimizeRect.y + minimizeRect.height);
|
|
|
|
AColor::Line(*dc, minimizeRect.x, minimizeRect.y - 1,
|
|
|
|
minimizeRect.x + minimizeRect.width, minimizeRect.y - 1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::DrawBackground(wxDC * dc, const wxRect r, bool bSelected,
|
2013-08-25 21:51:26 +00:00
|
|
|
bool WXUNUSED(bHasMuteSolo), const int labelw, const int WXUNUSED(vrul))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// fill in label
|
|
|
|
wxRect fill = r;
|
|
|
|
fill.width = labelw-4;
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::MediumTrackInfo(dc, bSelected);
|
2014-06-03 20:30:19 +00:00
|
|
|
dc->DrawRectangle(fill);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-16 23:08:33 +00:00
|
|
|
// Vaughan, 2010-09-16: No more bevels around controls area. Now only around buttons.
|
2010-09-12 05:11:43 +00:00
|
|
|
//if( bHasMuteSolo )
|
|
|
|
//{
|
2014-06-03 20:30:19 +00:00
|
|
|
// fill=wxRect( r.x+1, r.y+17, vrul-6, 32);
|
2010-09-12 05:11:43 +00:00
|
|
|
// AColor::BevelTrackInfo( *dc, true, fill );
|
2010-09-18 21:02:36 +00:00
|
|
|
//
|
2014-06-03 20:30:19 +00:00
|
|
|
// fill=wxRect( r.x+1, r.y+67, fill.width, r.height-87);
|
2010-09-12 05:11:43 +00:00
|
|
|
// AColor::BevelTrackInfo( *dc, true, fill );
|
|
|
|
//}
|
|
|
|
//else
|
|
|
|
//{
|
2014-06-03 20:30:19 +00:00
|
|
|
// fill=wxRect( r.x+1, r.y+17, vrul-6, r.height-37);
|
2010-09-12 05:11:43 +00:00
|
|
|
// AColor::BevelTrackInfo( *dc, true, fill );
|
|
|
|
//}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::GetTrackControlsRect(const wxRect r, wxRect & dest) const
|
|
|
|
{
|
|
|
|
wxRect top;
|
|
|
|
wxRect bot;
|
|
|
|
|
|
|
|
GetTitleBarRect(r, top);
|
2010-09-16 23:08:33 +00:00
|
|
|
GetMinimizeRect(r, bot);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
dest.x = r.x;
|
2010-09-16 23:08:33 +00:00
|
|
|
dest.width = kTrackInfoWidth - dest.x;
|
2010-09-18 21:02:36 +00:00
|
|
|
dest.y = top.GetBottom() + 2; // BUG
|
2010-01-23 19:44:49 +00:00
|
|
|
dest.height = bot.GetTop() - top.GetBottom() - 2;
|
|
|
|
}
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackInfo::DrawCloseBox(wxDC * dc, const wxRect r, bool down)
|
|
|
|
{
|
|
|
|
wxRect bev;
|
|
|
|
GetCloseBoxRect(r, bev);
|
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
|
|
wxPen pen( theTheme.Colour( clrTrackPanelText ));
|
|
|
|
dc->SetPen( pen );
|
|
|
|
#else
|
|
|
|
dc->SetPen(*wxBLACK_PEN);
|
|
|
|
#endif
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Draw the "X"
|
2010-01-23 19:44:49 +00:00
|
|
|
const int s = 6;
|
|
|
|
|
|
|
|
int ls = bev.x + ((bev.width - s) / 2);
|
|
|
|
int ts = bev.y + ((bev.height - s) / 2);
|
|
|
|
int rs = ls + s;
|
|
|
|
int bs = ts + s;
|
|
|
|
|
|
|
|
AColor::Line(*dc, ls, ts, rs, bs);
|
|
|
|
AColor::Line(*dc, ls + 1, ts, rs + 1, bs);
|
|
|
|
AColor::Line(*dc, rs, ts, ls, bs);
|
|
|
|
AColor::Line(*dc, rs + 1, ts, ls + 1, bs);
|
|
|
|
|
|
|
|
bev.Inflate(-1, -1);
|
|
|
|
AColor::BevelTrackInfo(*dc, !down, bev);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::DrawTitleBar(wxDC * dc, const wxRect r, Track * t,
|
|
|
|
bool down)
|
|
|
|
{
|
|
|
|
wxRect bev;
|
|
|
|
GetTitleBarRect(r, bev);
|
|
|
|
bev.Inflate(-1, -1);
|
|
|
|
|
|
|
|
// Draw title text
|
|
|
|
SetTrackInfoFont(dc);
|
|
|
|
wxString titleStr = t->GetName();
|
2010-09-16 23:08:33 +00:00
|
|
|
int allowableWidth = kTrackInfoWidth - 38 - kLeftInset;
|
2010-03-09 04:59:26 +00:00
|
|
|
|
2015-04-13 05:52:32 +00:00
|
|
|
wxCoord textWidth, textHeight;
|
2010-01-23 19:44:49 +00:00
|
|
|
dc->GetTextExtent(titleStr, &textWidth, &textHeight);
|
|
|
|
while (textWidth > allowableWidth) {
|
|
|
|
titleStr = titleStr.Left(titleStr.Length() - 1);
|
|
|
|
dc->GetTextExtent(titleStr, &textWidth, &textHeight);
|
|
|
|
}
|
|
|
|
// wxGTK leaves little scraps (antialiasing?) of the
|
|
|
|
// characters if they are repeatedly drawn. This
|
|
|
|
// happens when holding down mouse button and moving
|
|
|
|
// in and out of the title bar. So clear it first.
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::MediumTrackInfo(dc, t->GetSelected());
|
2010-01-23 19:44:49 +00:00
|
|
|
dc->DrawRectangle(bev);
|
2010-09-16 23:08:33 +00:00
|
|
|
dc->DrawText(titleStr, r.x + kTrackInfoBtnSize + 3, r.y + 2);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Pop-up triangle
|
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
|
|
wxColour c = theTheme.Colour( clrTrackPanelText );
|
|
|
|
#else
|
|
|
|
wxColour c = *wxBLACK;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
dc->SetPen(c);
|
|
|
|
dc->SetBrush(c);
|
|
|
|
|
|
|
|
int s = 10; // Width of dropdown arrow...height is half of width
|
|
|
|
AColor::Arrow(*dc,
|
|
|
|
bev.GetRight() - s - 3, // 3 to offset from right border
|
|
|
|
bev.y + ((bev.height - (s / 2)) / 2),
|
|
|
|
s);
|
|
|
|
|
|
|
|
AColor::BevelTrackInfo(*dc, !down, bev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw the Mute or the Solo button, depending on the value of solo.
|
|
|
|
void TrackInfo::DrawMuteSolo(wxDC * dc, const wxRect r, Track * t,
|
|
|
|
bool down, bool solo, bool bHasSoloButton)
|
|
|
|
{
|
|
|
|
wxRect bev;
|
|
|
|
if( solo && !bHasSoloButton )
|
|
|
|
return;
|
|
|
|
GetMuteSoloRect(r, bev, solo, bHasSoloButton);
|
|
|
|
bev.Inflate(-1, -1);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (bev.y + bev.height >= r.y + r.height - 19)
|
|
|
|
return; // don't draw mute and solo buttons, because they don't fit into track label
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::MediumTrackInfo( dc, t->GetSelected());
|
2010-01-23 19:44:49 +00:00
|
|
|
if( solo )
|
|
|
|
{
|
|
|
|
if( t->GetSolo() )
|
|
|
|
{
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::Solo(dc, t->GetSolo(), t->GetSelected());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( t->GetMute() )
|
|
|
|
{
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::Mute(dc, t->GetMute(), t->GetSelected(), t->GetSolo());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//(solo) ? AColor::Solo(dc, t->GetSolo(), t->GetSelected()) :
|
|
|
|
// AColor::Mute(dc, t->GetMute(), t->GetSelected(), t->GetSolo());
|
|
|
|
dc->SetPen( *wxTRANSPARENT_PEN );//No border!
|
|
|
|
dc->DrawRectangle(bev);
|
|
|
|
|
2015-04-13 05:52:32 +00:00
|
|
|
wxCoord textWidth, textHeight;
|
2012-03-20 15:36:02 +00:00
|
|
|
wxString str = (solo) ?
|
|
|
|
/* i18n-hint: This is on a button that will silence this track.*/
|
2014-06-03 20:30:19 +00:00
|
|
|
_("Solo") :
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: This is on a button that will silence all the other tracks.*/
|
|
|
|
_("Mute");
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
SetTrackInfoFont(dc);
|
|
|
|
dc->GetTextExtent(str, &textWidth, &textHeight);
|
|
|
|
dc->DrawText(str, bev.x + (bev.width - textWidth) / 2, bev.y);
|
|
|
|
|
|
|
|
AColor::BevelTrackInfo(*dc, (solo?t->GetSolo():t->GetMute()) == down, bev);
|
|
|
|
|
|
|
|
if (solo && !down) {
|
|
|
|
// Update the mute button, which may be grayed out depending on
|
|
|
|
// the state of the solo button.
|
|
|
|
DrawMuteSolo(dc, r, t, false, false, bHasSoloButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
(Sync-Lock)
Commented out the one call to TrackInfo::DrawBordersWithin(). This eliminates all the dark lines within the TrackInfo, in an effort to make the sync-lock icon not look like a button. It leaves some lighter borders, and I think that's an aesthetic improvement, though it make be worse in terms of accessibility. I can also remove the light border above the sync-lock icon, but I think this looks best overall.
In Track::IsSyncLockSelected(), for the "// Not in a sync-locked group." conditional, it returned true if the track was selected. I made it do so only if track kind is Wave or Label. Among other things, this means Time and Note tracks will never show the sync-lock icon. I think this is correct by definition, but Al, please let me know if this will have negative repercussions elsewhere. There are *lots* of calls to that method and I can move the track-type check to the code that draws the sync-lock icon..
Fixed the bug Gale pointed out where, if a WaveTrack is shrunk such that the sync-lock icon is over a TrackInfo control, such as pan slider, it didn't intercept the mouse event, and passed it on to the control. Now, clicking on the sync-lock icon does nothing.
Fixed a bug where the sync-lock icon was redrawn dark when the minimize button is down. Now not redrawn at all in that case.
Added some clarifying comments, especially about the term "Label" as used in TrackPanel.*.
2010-09-09 00:46:40 +00:00
|
|
|
// Draw the minimize button *and* the sync-lock track icon, if necessary.
|
2010-08-25 22:34:17 +00:00
|
|
|
void TrackInfo::DrawMinimize(wxDC * dc, const wxRect r, Track * t, bool down)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxRect bev;
|
2010-09-16 23:08:33 +00:00
|
|
|
GetMinimizeRect(r, bev);
|
2010-08-24 19:32:54 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Clear background to get rid of previous arrow
|
2010-03-09 04:59:26 +00:00
|
|
|
AColor::MediumTrackInfo(dc, t->GetSelected());
|
2010-01-23 19:44:49 +00:00
|
|
|
dc->DrawRectangle(bev);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
|
|
wxColour c = theTheme.Colour(clrTrackPanelText);
|
|
|
|
dc->SetBrush(c);
|
|
|
|
dc->SetPen(c);
|
|
|
|
#else
|
|
|
|
AColor::Dark(dc, t->GetSelected());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
AColor::Arrow(*dc,
|
|
|
|
bev.x - 5 + bev.width / 2,
|
|
|
|
bev.y - 2 + bev.height / 2,
|
|
|
|
10,
|
2010-08-25 22:34:17 +00:00
|
|
|
t->GetMinimized());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
AColor::BevelTrackInfo(*dc, !down, bev);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::MakeMoreSliders()
|
|
|
|
{
|
|
|
|
wxRect r(0, 0, 1000, 1000);
|
|
|
|
wxRect gainRect;
|
|
|
|
wxRect panRect;
|
|
|
|
|
|
|
|
GetGainRect(r, gainRect);
|
|
|
|
GetPanRect(r, panRect);
|
|
|
|
|
|
|
|
/* i18n-hint: Title of the Gain slider, used to adjust the volume */
|
|
|
|
LWSlider *slider = new LWSlider(pParent, _("Gain"),
|
|
|
|
wxPoint(gainRect.x, gainRect.y),
|
|
|
|
wxSize(gainRect.width, gainRect.height),
|
|
|
|
DB_SLIDER);
|
|
|
|
slider->SetDefaultValue(1.0);
|
|
|
|
mGains.Add(slider);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/* i18n-hint: Title of the Pan slider, used to move the sound left or right */
|
|
|
|
slider = new LWSlider(pParent, _("Pan"),
|
|
|
|
wxPoint(panRect.x, panRect.y),
|
|
|
|
wxSize(panRect.width, panRect.height),
|
|
|
|
PAN_SLIDER);
|
|
|
|
slider->SetDefaultValue(0.0);
|
|
|
|
mPans.Add(slider);
|
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
// This covers the case where kInitialSliders - kSliderPageFlip is not big
|
|
|
|
// enough
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackInfo::EnsureSufficientSliders(int index)
|
|
|
|
{
|
2010-04-21 05:03:24 +00:00
|
|
|
while (mGains.Count() < (unsigned int)index - mSliderOffset + 1 ||
|
|
|
|
mPans.Count() < (unsigned int)index - mSliderOffset + 1)
|
2010-01-23 19:44:49 +00:00
|
|
|
MakeMoreSliders();
|
|
|
|
}
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|
|
void TrackInfo::DrawVelocitySlider(wxDC *dc, NoteTrack *t, wxRect r)
|
|
|
|
{
|
|
|
|
wxRect gainRect;
|
|
|
|
int index = t->GetIndex();
|
|
|
|
EnsureSufficientSliders(index);
|
|
|
|
GetGainRect(r, gainRect);
|
|
|
|
if (gainRect.y + gainRect.height < r.y + r.height - 19) {
|
|
|
|
mGains[index]->SetStyle(VEL_SLIDER);
|
|
|
|
GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y));
|
|
|
|
GainSlider(index)->Set(t->GetGain());
|
|
|
|
GainSlider(index)->OnPaint(*dc, t->GetSelected());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void TrackInfo::DrawSliders(wxDC *dc, WaveTrack *t, wxRect r)
|
|
|
|
{
|
|
|
|
wxRect gainRect;
|
|
|
|
wxRect panRect;
|
|
|
|
int index = t->GetIndex();
|
|
|
|
|
|
|
|
EnsureSufficientSliders( index );
|
|
|
|
|
|
|
|
GetGainRect(r, gainRect);
|
|
|
|
GetPanRect(r, panRect);
|
|
|
|
|
|
|
|
if (gainRect.y + gainRect.height < r.y + r.height - 19) {
|
2010-10-28 17:34:35 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2010-09-18 21:02:36 +00:00
|
|
|
GainSlider(index)->SetStyle(DB_SLIDER);
|
|
|
|
#endif
|
2010-04-21 05:03:24 +00:00
|
|
|
GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y));
|
|
|
|
GainSlider(index)->Set(t->GetGain());
|
|
|
|
GainSlider(index)->OnPaint(*dc, t->GetSelected());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (panRect.y + panRect.height < r.y + r.height - 19) {
|
2010-04-21 05:03:24 +00:00
|
|
|
PanSlider(index)->Move(wxPoint(panRect.x, panRect.y));
|
|
|
|
PanSlider(index)->Set(t->GetPan());
|
|
|
|
PanSlider(index)->OnPaint(*dc, t->GetSelected());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TrackInfo::UpdateSliderOffset(Track *t)
|
|
|
|
{
|
|
|
|
if (!t)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Ensure that the specified track is: (a) at least the second track in the
|
|
|
|
// arrays (if it's the second track in a stereo pair the first must be in);
|
|
|
|
// (b) no farther in than kSliderPageFlip
|
|
|
|
int newSliderOffset = (int)mSliderOffset;
|
|
|
|
if ((unsigned int)t->GetIndex() < mSliderOffset + 1 ||
|
|
|
|
(unsigned int)t->GetIndex() > mSliderOffset + kSliderPageFlip) {
|
|
|
|
newSliderOffset = t->GetIndex() - (int)kSliderPageFlip / 2;
|
|
|
|
}
|
|
|
|
// Slider offset can't be negative
|
|
|
|
if (newSliderOffset < 0) newSliderOffset = 0;
|
|
|
|
|
|
|
|
// Rotate the array values if necessary
|
|
|
|
int delta = newSliderOffset - (int)mSliderOffset;
|
|
|
|
|
|
|
|
// If the rotation is greater than the array size, none of the old values
|
|
|
|
// are preserved, so don't bother rotating.
|
|
|
|
if (abs(delta) >= (int)mGains.Count())
|
|
|
|
delta = 0;
|
|
|
|
|
|
|
|
if (delta > 0) {
|
|
|
|
// Circularly shift values down in the arrays (temp arrays needed to
|
|
|
|
// avoid reading written-over values)
|
|
|
|
LWSliderArray tempGains;
|
|
|
|
LWSliderArray tempPans;
|
|
|
|
tempGains.SetCount(delta);
|
|
|
|
tempPans.SetCount(delta);
|
2013-10-29 21:49:45 +00:00
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
for (int i = 0; i < (int)mGains.Count(); ++i) {
|
|
|
|
int src = (i + delta) % (int)mGains.Count();
|
|
|
|
if (src < 0) src += (int)mGains.Count();
|
|
|
|
|
|
|
|
if (i < delta) {
|
|
|
|
// Save a copy of the first `delta` values
|
|
|
|
tempGains[i] = mGains[i];
|
|
|
|
tempPans[i] = mPans[i];
|
|
|
|
}
|
|
|
|
if (src >= delta) {
|
|
|
|
// These values have not been overwritten
|
|
|
|
mGains[i] = mGains[src];
|
|
|
|
mPans[i] = mPans[src];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// These ones have
|
|
|
|
mGains[i] = tempGains[src];
|
|
|
|
mPans[i] = tempPans[src];
|
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2010-04-21 05:03:24 +00:00
|
|
|
else if (delta < 0) {
|
|
|
|
// Circularly shift values up in the arrays
|
|
|
|
LWSliderArray tempGains;
|
|
|
|
LWSliderArray tempPans;
|
|
|
|
tempGains.SetCount(mGains.Count());
|
|
|
|
tempPans.SetCount(mPans.Count());
|
|
|
|
|
|
|
|
// Iterating backwards to do this
|
|
|
|
for (int i = mGains.Count() - 1; i >= 0; --i) {
|
|
|
|
int src = (i + delta) % (int)mGains.Count();
|
|
|
|
if (src < 0) src += (int)mGains.Count();
|
|
|
|
|
|
|
|
if (i >= (int)mGains.Count() + delta) {
|
|
|
|
// Save a copy of the last `delta` values
|
|
|
|
tempGains[i] = mGains[i];
|
|
|
|
tempPans[i] = mPans[i];
|
|
|
|
}
|
|
|
|
if (src < (int)mGains.Count() + delta) {
|
|
|
|
// These values have not been overwritten
|
|
|
|
mGains[i] = mGains[src];
|
|
|
|
mPans[i] = mPans[src];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// These ones have
|
|
|
|
mGains[i] = tempGains[src];
|
|
|
|
mPans[i] = tempPans[src];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mSliderOffset = newSliderOffset;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2010-04-21 05:03:24 +00:00
|
|
|
LWSlider * TrackInfo::GainSlider(int trackIndex)
|
|
|
|
{
|
|
|
|
return mGains[trackIndex - mSliderOffset];
|
|
|
|
}
|
|
|
|
|
|
|
|
LWSlider * TrackInfo::PanSlider(int trackIndex)
|
|
|
|
{
|
|
|
|
return mPans[trackIndex - mSliderOffset];
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-09-21 19:34:12 +00:00
|
|
|
static TrackPanel * TrackPanelFactory(wxWindow * parent,
|
2011-11-25 20:40:26 +00:00
|
|
|
wxWindowID id,
|
|
|
|
const wxPoint & pos,
|
|
|
|
const wxSize & size,
|
|
|
|
TrackList * tracks,
|
|
|
|
ViewInfo * viewInfo,
|
|
|
|
TrackPanelListener * listener,
|
|
|
|
AdornedRulerPanel * ruler)
|
|
|
|
{
|
|
|
|
return new TrackPanel(
|
|
|
|
parent,
|
|
|
|
id,
|
|
|
|
pos,
|
|
|
|
size,
|
|
|
|
tracks,
|
|
|
|
viewInfo,
|
|
|
|
listener,
|
|
|
|
ruler);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Declare the static factory function.
|
|
|
|
// We defined it in the class.
|
|
|
|
TrackPanel *(*TrackPanel::FactoryFunction)(
|
|
|
|
wxWindow * parent,
|
|
|
|
wxWindowID id,
|
|
|
|
const wxPoint & pos,
|
|
|
|
const wxSize & size,
|
|
|
|
TrackList * tracks,
|
|
|
|
ViewInfo * viewInfo,
|
|
|
|
TrackPanelListener * listener,
|
|
|
|
AdornedRulerPanel * ruler) = TrackPanelFactory;
|
|
|
|
|