diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp index 5d16b098c35f..2ba4ef096513 100644 --- a/src/AdornedRulerPanel.cpp +++ b/src/AdornedRulerPanel.cpp @@ -55,6 +55,8 @@ #include "widgets/AButton.h" #include "widgets/AudacityMessageBox.h" #include "widgets/Grabber.h" +#include "widgets/LinearUpdater.h" +#include "widgets/LogarithmicUpdater.h" #include "widgets/wxWidgetsWindowPlacement.h" #include @@ -1280,9 +1282,9 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* project, mOuter = GetClientRect(); - mRuler.SetUseZoomInfo(mLeftOffset, mViewInfo); + mRuler.SetUpdater( std::make_unique( mViewInfo ), mLeftOffset ); mRuler.SetLabelEdges( false ); - mRuler.SetFormat( Ruler::TimeFormat ); + mRuler.SetFormat( TimeFormat ); mTracks = &TrackList::Get( *project ); @@ -2577,7 +2579,7 @@ int AdornedRulerPanel::GetRulerHeight(bool showScrubBar) void AdornedRulerPanel::SetLeftOffset(int offset) { mLeftOffset = offset; - mRuler.SetUseZoomInfo(offset, mViewInfo); + mRuler.SetUpdater( std::make_unique( mViewInfo ), offset ); } // Draws the scrubbing/seeking indicator. diff --git a/src/AudacityHeaders.h b/src/AudacityHeaders.h index 7a7d7d6dce9c..fac0071b771d 100644 --- a/src/AudacityHeaders.h +++ b/src/AudacityHeaders.h @@ -57,7 +57,6 @@ #include "UndoManager.h" #include "WaveTrack.h" #include "widgets/ASlider.h" -#include "widgets/Ruler.h" // PRL: These lines allow you to remove Project.h above. // They must be included before the definition of macro NEW below. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c2262b211e43..a7d2145ebb40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1013,6 +1013,12 @@ list( APPEND SOURCES widgets/BackedPanel.h widgets/BasicMenu.cpp widgets/BasicMenu.h + widgets/CustomUpdater.cpp + widgets/CustomUpdater.h + widgets/CustomUpdaterPosition.cpp + widgets/CustomUpdaterPosition.h + widgets/CustomUpdaterValue.cpp + widgets/CustomUpdaterValue.h widgets/ErrorDialog.cpp widgets/ErrorDialog.h widgets/ExpandingToolBar.cpp @@ -1045,6 +1051,10 @@ list( APPEND SOURCES widgets/ImageRoll.h widgets/KeyView.cpp widgets/KeyView.h + widgets/LinearUpdater.cpp + widgets/LinearUpdater.h + widgets/LogarithmicUpdater.cpp + widgets/LogarithmicUpdater.h widgets/MeterPanel.cpp widgets/MeterPanel.h widgets/MeterPanelBase.cpp @@ -1064,12 +1074,16 @@ list( APPEND SOURCES widgets/ReadOnlyText.h widgets/Ruler.cpp widgets/Ruler.h + widgets/RulerPanel.cpp + widgets/RulerPanel.h $<$: widgets/ErrorReportDialog.cpp widgets/ErrorReportDialog.h > widgets/UnwritableLocationErrorDialog.cpp widgets/UnwritableLocationErrorDialog.h + widgets/Updater.cpp + widgets/Updater.h widgets/VetoDialogHook.h widgets/Warning.cpp widgets/Warning.h diff --git a/src/FreqWindow.cpp b/src/FreqWindow.cpp index 335df93968cf..34921b947a59 100644 --- a/src/FreqWindow.cpp +++ b/src/FreqWindow.cpp @@ -82,7 +82,9 @@ the mouse around. #include "./widgets/HelpSystem.h" #include "widgets/AudacityMessageBox.h" -#include "widgets/Ruler.h" +#include "widgets/RulerPanel.h" +#include "widgets/LinearUpdater.h" +#include "widgets/LogarithmicUpdater.h" #include "widgets/VetoDialogHook.h" #if wxUSE_ACCESSIBILITY @@ -293,7 +295,7 @@ void FrequencyPlotDialog::Populate() S.GetParent(), wxID_ANY, wxVERTICAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ 0.0, -dBRange }, - Ruler::LinearDBFormat, + LinearDBFormat, XO("dB"), RulerPanel::Options{} .LabelEdges(true) @@ -376,7 +378,7 @@ void FrequencyPlotDialog::Populate() S.GetParent(), wxID_ANY, wxHORIZONTAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ 10, 20000 }, - Ruler::RealFormat, + RealFormat, XO("Hz"), RulerPanel::Options{} .Log(true) @@ -674,10 +676,10 @@ void FrequencyPlotDialog::DrawPlot() if (!mData || mDataLen < mWindowSize || mAnalyst->GetProcessedSize() == 0) { wxMemoryDC memDC; - vRuler->ruler.SetLog(false); + vRuler->ruler.SetUpdater(std::make_unique()); vRuler->ruler.SetRange(0.0, -dBRange); - hRuler->ruler.SetLog(false); + hRuler->ruler.SetUpdater(std::make_unique()); hRuler->ruler.SetRange(0, 1); DrawBackground(memDC); @@ -714,10 +716,10 @@ void FrequencyPlotDialog::DrawPlot() if (mAlg == SpectrumAnalyst::Spectrum) { vRuler->ruler.SetUnits(XO("dB")); - vRuler->ruler.SetFormat(Ruler::LinearDBFormat); + vRuler->ruler.SetFormat(LinearDBFormat); } else { vRuler->ruler.SetUnits({}); - vRuler->ruler.SetFormat(Ruler::RealFormat); + vRuler->ruler.SetFormat(RealFormat); } int w1, w2, h; vRuler->ruler.GetMaxSize(&w1, &h); @@ -752,19 +754,19 @@ void FrequencyPlotDialog::DrawPlot() if (mLogAxis) { xStep = pow(2.0f, (log(xRatio) / log(2.0f)) / width); - hRuler->ruler.SetLog(true); + hRuler->ruler.SetUpdater(std::make_unique()); } else { xStep = (xMax - xMin) / width; - hRuler->ruler.SetLog(false); + hRuler->ruler.SetUpdater(std::make_unique()); } hRuler->ruler.SetUnits(XO("Hz")); } else { xMin = 0; xMax = mAnalyst->GetProcessedSize() / mRate; xStep = (xMax - xMin) / width; - hRuler->ruler.SetLog(false); + hRuler->ruler.SetUpdater(std::make_unique()); /* i18n-hint: short form of 'seconds'.*/ hRuler->ruler.SetUnits(XO("s")); } diff --git a/src/Printing.cpp b/src/Printing.cpp index 98797879c708..b72d553b24e8 100644 --- a/src/Printing.cpp +++ b/src/Printing.cpp @@ -83,7 +83,7 @@ bool AudacityPrintout::OnPrintPage(int WXUNUSED(page)) ruler.SetBounds(0, 0, width, rulerPageHeight); ruler.SetOrientation(wxHORIZONTAL); ruler.SetRange(0.0, mTracks->GetEndTime()); - ruler.SetFormat(Ruler::TimeFormat); + ruler.SetFormat(TimeFormat); ruler.SetLabelEdges(true); ruler.Draw(*dc); diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index bc80c566b2c0..2d74e0708181 100644 --- a/src/TimeTrack.cpp +++ b/src/TimeTrack.cpp @@ -22,6 +22,7 @@ #include #include #include "widgets/Ruler.h" +#include "widgets/LinearUpdater.h" #include "Envelope.h" #include "Mix.h" #include "Project.h" @@ -74,9 +75,9 @@ void TimeTrack::CleanState() SetName(GetDefaultName()); mRuler = std::make_unique(); - mRuler->SetUseZoomInfo(0, mZoomInfo); + mRuler->SetUpdater(std::make_unique( mZoomInfo ), 0); mRuler->SetLabelEdges(false); - mRuler->SetFormat(Ruler::TimeFormat); + mRuler->SetFormat(TimeFormat); } TimeTrack::TimeTrack(const TimeTrack &orig, ProtectedCreationArg &&a, @@ -102,9 +103,9 @@ TimeTrack::TimeTrack(const TimeTrack &orig, ProtectedCreationArg &&a, ///@TODO: Give Ruler:: a copy-constructor instead of this? mRuler = std::make_unique(); - mRuler->SetUseZoomInfo(0, mZoomInfo); + mRuler->SetUpdater(std::make_unique( mZoomInfo ), 0); mRuler->SetLabelEdges(false); - mRuler->SetFormat(Ruler::TimeFormat); + mRuler->SetFormat(TimeFormat); } // Copy the track metadata but not the contents. diff --git a/src/commands/SetTrackInfoCommand.cpp b/src/commands/SetTrackInfoCommand.cpp index d2099abab49b..6fc37f8b54ba 100644 --- a/src/commands/SetTrackInfoCommand.cpp +++ b/src/commands/SetTrackInfoCommand.cpp @@ -286,17 +286,20 @@ static const EnumValueSymbol kColourStrings[nColours] = enum kScaleTypes { - kLinear, - kDb, + kLinearAmp, + kLogarithmicDb, + kLinearDb, nScaleTypes }; static const EnumValueSymbol kScaleTypeStrings[nScaleTypes] = { // These are acceptable dual purpose internal/visible names - { XO("Linear") }, + { XO("Linear (amp)") }, /* i18n-hint: abbreviates decibels */ - { XO("dB") }, + { XO("Logarithmic (dB)") }, + /* i18n-hint: abbreviates decibels */ + { XO("Linear (dB)")} }; enum kZoomTypes @@ -333,7 +336,7 @@ bool SetTrackVisualsCommand::VisitSettings( SettingsVisitorBase & S ){ S.OptionalN( bHasDisplayType ).DefineEnum( mDisplayType, wxT("Display"), 0, symbols.data(), symbols.size() ); } - S.OptionalN( bHasScaleType ).DefineEnum( mScaleType, wxT("Scale"), kLinear, kScaleTypeStrings, nScaleTypes ); + S.OptionalN( bHasScaleType ).DefineEnum( mScaleType, wxT("Scale"), kLinearAmp, kScaleTypeStrings, nScaleTypes ); S.OptionalN( bHasColour ).DefineEnum( mColour, wxT("Color"), kColour0, kColourStrings, nColours ); S.OptionalN( bHasVZoom ).DefineEnum( mVZoom, wxT("VZoom"), kReset, kZoomTypeStrings, nZoomTypes ); S.OptionalN( bHasVZoomTop ).Define( mVZoomTop, wxT("VZoomHigh"), 1.0, -2.0, 2.0 ); @@ -422,11 +425,14 @@ bool SetTrackVisualsCommand::ApplyInner(const CommandContext & context, Track * view.SetDisplay( WaveTrackSubViewType::Default(), false ); } } - if( wt && bHasScaleType ) - wt->GetWaveformSettings().scaleType = - (mScaleType==kLinear) ? - WaveformSettings::stLinear - : WaveformSettings::stLogarithmic; + if (wt && bHasScaleType) { + switch (mScaleType) { + default: + case kLinearAmp: wt->GetWaveformSettings().scaleType = WaveformSettings::stLinearAmp; + case kLogarithmicDb: wt->GetWaveformSettings().scaleType = WaveformSettings::stLogarithmicDb; + case kLinearDb: wt->GetWaveformSettings().scaleType = WaveformSettings::stLinearDb; + } + } if( wt && bHasVZoom ){ switch( mVZoom ){ diff --git a/src/effects/Compressor.cpp b/src/effects/Compressor.cpp index 71a800847a24..fc3b372ac925 100644 --- a/src/effects/Compressor.cpp +++ b/src/effects/Compressor.cpp @@ -649,7 +649,7 @@ void EffectCompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) vRuler.SetBounds(0, 0, width, height); vRuler.SetOrientation(wxVERTICAL); vRuler.SetRange(0, -rangeDB); - vRuler.SetFormat(Ruler::LinearDBFormat); + vRuler.SetFormat(LinearDBFormat); vRuler.SetUnits(XO("dB")); vRuler.GetMaxSize(&w, NULL); @@ -657,7 +657,7 @@ void EffectCompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) hRuler.SetBounds(0, 0, width, height); hRuler.SetOrientation(wxHORIZONTAL); hRuler.SetRange(-rangeDB, 0); - hRuler.SetFormat(Ruler::LinearDBFormat); + hRuler.SetFormat(LinearDBFormat); hRuler.SetUnits(XO("dB")); hRuler.SetFlip(true); hRuler.GetMaxSize(NULL, &h); diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp index 946813a8ef84..62bea221ce97 100644 --- a/src/effects/Equalization.cpp +++ b/src/effects/Equalization.cpp @@ -103,7 +103,9 @@ #include "../WaveClip.h" #include "ViewInfo.h" #include "../WaveTrack.h" -#include "../widgets/Ruler.h" +#include "../widgets/LinearUpdater.h" +#include "../widgets/LogarithmicUpdater.h" +#include "../widgets/RulerPanel.h" #include "../widgets/AudacityTextEntryDialog.h" #include "XMLFileReader.h" #include "AllThemeResources.h" @@ -722,7 +724,7 @@ std::unique_ptr EffectEqualization::PopulateOrExchange( S.GetParent(), wxID_ANY, wxVERTICAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ 60.0, -120.0 }, - Ruler::LinearDBFormat, + LinearDBFormat, XO("dB"), RulerPanel::Options{} .LabelEdges(true) @@ -779,7 +781,7 @@ std::unique_ptr EffectEqualization::PopulateOrExchange( S.GetParent(), wxID_ANY, wxHORIZONTAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ mLoFreq, mHiFreq }, - Ruler::IntFormat, + IntFormat, XO("Hz"), RulerPanel::Options{} .Log(true) @@ -2288,7 +2290,7 @@ void EffectEqualization::UpdateDraw() { EnvLogToLin(); mEnvelope = mLinEnvelope.get(); - mFreqRuler->ruler.SetLog(false); + mFreqRuler->ruler.SetUpdater(std::make_unique()); mFreqRuler->ruler.SetRange(0, mHiFreq); } @@ -2320,7 +2322,7 @@ void EffectEqualization::UpdateGraphic() EnvLinToLog(); mEnvelope = mLogEnvelope.get(); - mFreqRuler->ruler.SetLog(true); + mFreqRuler->ruler.SetUpdater(std::make_unique()); mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); } @@ -2909,7 +2911,7 @@ void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event)) mLin = mLinFreq->IsChecked(); if(IsLinear()) //going from log to lin freq scale { - mFreqRuler->ruler.SetLog(false); + mFreqRuler->ruler.SetUpdater(std::make_unique()); mFreqRuler->ruler.SetRange(0, mHiFreq); EnvLogToLin(); mEnvelope = mLinEnvelope.get(); @@ -2917,7 +2919,7 @@ void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event)) } else //going from lin to log freq scale { - mFreqRuler->ruler.SetLog(true); + mFreqRuler->ruler.SetUpdater(std::make_unique()); mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); EnvLinToLog(); mEnvelope = mLogEnvelope.get(); diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp index b6a79b99a34f..3c0109993e60 100644 --- a/src/effects/ScienFilter.cpp +++ b/src/effects/ScienFilter.cpp @@ -61,7 +61,7 @@ a graph for EffectScienFilter. #include "../WaveTrack.h" #include "../widgets/valnum.h" #include "../widgets/AudacityMessageBox.h" -#include "../widgets/Ruler.h" +#include "../widgets/RulerPanel.h" #include "../widgets/WindowAccessible.h" #if !defined(M_PI) @@ -278,7 +278,7 @@ std::unique_ptr EffectScienFilter::PopulateOrExchange( S.GetParent(), wxID_ANY, wxVERTICAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ 30.0, -120.0 }, - Ruler::LinearDBFormat, + LinearDBFormat, XO("dB"), RulerPanel::Options{} .LabelEdges(true) @@ -336,7 +336,7 @@ std::unique_ptr EffectScienFilter::PopulateOrExchange( S.GetParent(), wxID_ANY, wxHORIZONTAL, wxSize{ 100, 100 }, // Ruler can't handle small sizes RulerPanel::Range{ mLoFreq, mNyquist }, - Ruler::IntFormat, + IntFormat, {}, RulerPanel::Options{} .Log(true) diff --git a/src/prefs/TracksPrefs.cpp b/src/prefs/TracksPrefs.cpp index 42f5823f1fa7..3c8aea7534cd 100644 --- a/src/prefs/TracksPrefs.cpp +++ b/src/prefs/TracksPrefs.cpp @@ -55,21 +55,24 @@ namespace { namespace { const auto waveformScaleKey = wxT("/GUI/DefaultWaveformScaleChoice"); - const auto dbValueString = wxT("dB"); + const auto dbLogValueString = wxT("dBLog"); + const auto dbLinValueString = wxT("dBLin"); } static EnumSetting< WaveformSettings::ScaleTypeValues > waveformScaleSetting{ waveformScaleKey, { - { XO("Linear") }, - { dbValueString, XO("Logarithmic (dB)") }, + { XO("Linear (amp)") }, + { dbLogValueString, XO("Logarithmic (dB)") }, + { dbLinValueString, XO("Linear (dB)") }, }, 0, // linear { - WaveformSettings::stLinear, - WaveformSettings::stLogarithmic, + WaveformSettings::stLinearAmp, + WaveformSettings::stLogarithmicDb, + WaveformSettings::stLinearDb, } }; @@ -134,7 +137,7 @@ class TracksViewModeEnumSetting if ( !gPrefs->Read( key3, &value ) ) { if (newValue == obsoleteValue) { newValue = waveformSymbol.Internal(); - gPrefs->Write(waveformScaleKey, dbValueString); + gPrefs->Write(waveformScaleKey, dbLogValueString); } Write( value = newValue ); diff --git a/src/prefs/WaveformPrefs.cpp b/src/prefs/WaveformPrefs.cpp index f366e89ba648..1497b11c758e 100644 --- a/src/prefs/WaveformPrefs.cpp +++ b/src/prefs/WaveformPrefs.cpp @@ -243,7 +243,8 @@ void WaveformPrefs::OnDefaults(wxCommandEvent &) void WaveformPrefs::EnableDisableRange() { mRangeChoice->Enable - (mScaleChoice->GetSelection() == WaveformSettings::stLogarithmic); + (mScaleChoice->GetSelection() == WaveformSettings::stLogarithmicDb); + // What does this mean? } BEGIN_EVENT_TABLE(WaveformPrefs, PrefsPanel) diff --git a/src/prefs/WaveformSettings.cpp b/src/prefs/WaveformSettings.cpp index 1785f1afe1ab..3599502b3eac 100644 --- a/src/prefs/WaveformSettings.cpp +++ b/src/prefs/WaveformSettings.cpp @@ -163,8 +163,9 @@ const EnumValueSymbols &WaveformSettings::GetScaleNames() { static const EnumValueSymbols result{ // Keep in correspondence with ScaleTypeValues: - XO("Linear"), - XO("dB"), + XO("Linear (amp)"), + XO("Logarithmic (dB)"), + XO("Linear (dB)"), }; return result; } diff --git a/src/prefs/WaveformSettings.h b/src/prefs/WaveformSettings.h index 67d0f4bd1699..f5f7d8ec79e3 100644 --- a/src/prefs/WaveformSettings.h +++ b/src/prefs/WaveformSettings.h @@ -56,8 +56,9 @@ class AUDACITY_DLL_API WaveformSettings : public PrefsListener typedef int ScaleType; enum ScaleTypeValues : int { - stLinear, - stLogarithmic, + stLinearAmp, + stLogarithmicDb, + stLinearDb, stNumScaleTypes, }; @@ -68,6 +69,7 @@ class AUDACITY_DLL_API WaveformSettings : public PrefsListener int dBRange; // Convenience - bool isLinear() const { return stLinear == scaleType; } + bool isLinear() const { return scaleType == stLinearAmp || scaleType == stLinearDb; } + bool isAmp() const { return scaleType == stLinearAmp; } }; #endif diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp index 9d203217a0f4..3d4ad076a573 100644 --- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp @@ -20,6 +20,8 @@ Paul Licameli split from WaveTrackVRulerControls.cpp #include "../../../../WaveTrack.h" #include "../../../../prefs/SpectrogramSettings.h" #include "../../../../widgets/Ruler.h" +#include "../../../../widgets/LinearUpdater.h" +#include "../../../../widgets/LogarithmicUpdater.h" SpectrumVRulerControls::~SpectrumVRulerControls() = default; @@ -154,7 +156,7 @@ void SpectrumVRulerControls::DoUpdateVRuler( */ vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height - 1); vruler->SetOrientation(wxVERTICAL); - vruler->SetFormat(Ruler::RealFormat); + vruler->SetFormat(RealFormat); vruler->SetLabelEdges(true); // use kHz in scale, if appropriate if (maxFreq >= 2000) { @@ -167,7 +169,7 @@ void SpectrumVRulerControls::DoUpdateVRuler( vruler->SetRange((int)(maxFreq), (int)(minFreq)); vruler->SetUnits({}); } - vruler->SetLog(false); + vruler->SetUpdater(std::make_unique()); } break; case SpectrogramSettings::stLogarithmic: @@ -185,11 +187,11 @@ void SpectrumVRulerControls::DoUpdateVRuler( */ vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height - 1); vruler->SetOrientation(wxVERTICAL); - vruler->SetFormat(Ruler::IntFormat); + vruler->SetFormat(IntFormat); vruler->SetLabelEdges(true); vruler->SetRange(maxFreq, minFreq); vruler->SetUnits({}); - vruler->SetLog(true); + vruler->SetUpdater(std::make_unique()); NumberScale scale( wt->GetSpectrogramSettings().GetScale( minFreq, maxFreq ) .Reversal() ); diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp index e0b8691c3cb6..06cd31f635e1 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp @@ -21,9 +21,46 @@ Paul Licameli split from WaveTrackVRulerControls.cpp #include "../../../../WaveTrack.h" #include "../../../../prefs/WaveformSettings.h" #include "../../../../widgets/Ruler.h" +#include "../../../../widgets/LinearUpdater.h" +#include "../../../../widgets/CustomUpdaterValue.h" WaveformVRulerControls::~WaveformVRulerControls() = default; +// These are doubles beacuse of the type of value in Label, +// but for the purpose of labelling the linear dB waveform ruler, +// these should always be integer numbers. +using LinearDBValues = std::vector; + +static LinearDBValues majorValues{}, minorValues{}, minorMinorValues{}; + +void RegenerateLinearDBValues(int dBRange) +{ + majorValues.clear(); + minorValues.clear(); + minorMinorValues.clear(); + + majorValues.push_back(0); + majorValues.push_back(-dBRange); + majorValues.push_back(2 * -dBRange); + + const double EPSILON = .01; + + for (double major = 0.1; major <= 2 + EPSILON; major += .1) { + double val = std::round(major * 10) / 10; + if (fabs(major - 1) > EPSILON) + majorValues.push_back(std::trunc(-dBRange * val)); + } + for (double minor = 0.05; minor <= 1.95 + EPSILON; minor += .1) { + double val = std::round(minor * 100) / 100; + minorValues.push_back(std::trunc(-dBRange * val)); + } + for (int minorMinor = 0; minorMinor <= 2 * dBRange; minorMinor++) { + if ((minorMinor % (int) std::round(dBRange / 20)) != 0) { + minorMinorValues.push_back(-minorMinor); + } + } +} + std::vector WaveformVRulerControls::HitTest( const TrackPanelMouseState &st, const AudacityProject *pProject) @@ -74,7 +111,7 @@ unsigned WaveformVRulerControls::DoHandleWheelRotation( using namespace WaveTrackViewConstants; const bool isDB = - wt->GetWaveformSettings().scaleType == WaveformSettings::stLogarithmic; + wt->GetWaveformSettings().isLinear(); // Special cases for Waveform dB only. // Set the bottom of the dB scale but only if it's visible if (isDB && event.ShiftDown() && event.CmdDown()) { @@ -183,15 +220,22 @@ void WaveformVRulerControls::DoUpdateVRuler( const float dBRange = wt->GetWaveformSettings().dBRange; + if (dBRange != wt->GetLastdBRange()) + { + wt->SetLastdBRange(); + RegenerateLinearDBValues(dBRange); + } + WaveformSettings::ScaleType scaleType = wt->GetWaveformSettings().scaleType; - if (scaleType == WaveformSettings::stLinear) { + if (wt->GetWaveformSettings().isLinear()) { // Waveform float min, max; wt->GetDisplayBounds(&min, &max); - if (wt->GetLastScaleType() != scaleType && + if (wt->GetLastScaleType() != WaveformSettings::stLinearAmp && + wt->GetLastScaleType() != WaveformSettings::stLinearDb && wt->GetLastScaleType() != -1) { // do a translation into the linear space @@ -219,22 +263,62 @@ void WaveformVRulerControls::DoUpdateVRuler( vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height - 1); vruler->SetOrientation(wxVERTICAL); vruler->SetRange(max, min); - vruler->SetFormat(Ruler::RealFormat); - vruler->SetUnits({}); - vruler->SetLabelEdges(false); - vruler->SetLog(false); + vruler->SetFormat(RealFormat); + if (scaleType == WaveformSettings::stLinearAmp) { + vruler->SetLabelEdges(false); + vruler->SetUnits({}); + vruler->SetUpdater(std::make_unique()); + } + else { + vruler->SetLabelEdges(true); + vruler->SetUnits(XO("dB")); + vruler->SetUpdater(std::make_unique()); + std::vector values = { majorValues, minorValues, minorMinorValues }; + for (int ii = 0; ii < 3; ii++) { + Updater::Labels labs; + int size = (ii == 0) ? majorValues.size() : + (ii == 1) ? minorValues.size() : minorMinorValues.size(); + for (int i = 0; i < size; i++) { + double value = (ii == 0) ? majorValues[i] : + (ii == 1) ? minorValues[i] : minorMinorValues[i]; + Updater::Label lab; + + if (value == -dBRange) + lab.value = 0; + else { + float sign = (value > -dBRange ? 1 : -1); + if (value < -dBRange) + value = -2 * dBRange - value; + lab.value = DB_TO_LINEAR(value) * sign; + } + + wxString s = (value == -dBRange) ? + wxString(L"-\u221e") : wxString::FromDouble(value); + // \u221e represents the infinity symbol + // Should this just be -dBRange so it is consistent? + //wxString s = wxString::FromDouble(value); + lab.text = Verbatim(s); + + labs.push_back(lab); + } + if (ii == 0) + vruler->SetCustomMajorLabels(labs); + else if (ii == 1) + vruler->SetCustomMinorLabels(labs); + else + vruler->SetCustomMinorMinorLabels(labs); + } + } } else { - wxASSERT(scaleType == WaveformSettings::stLogarithmic); - scaleType = WaveformSettings::stLogarithmic; - - vruler->SetUnits({}); + vruler->SetUnits(XO("dB")); float min, max; wt->GetDisplayBounds(&min, &max); float lastdBRange; - if (wt->GetLastScaleType() != scaleType && + if (wt->GetLastScaleType() != WaveformSettings::stLogarithmicDb && + // When Logarithmic Amp happens, put that here wt->GetLastScaleType() != -1) { // do a translation into the dB space @@ -328,9 +412,9 @@ void WaveformVRulerControls::DoUpdateVRuler( else vruler->SetBounds(0.0, 0.0, 0.0, 0.0); // A.C.H I couldn't find a way to just disable it? #endif - vruler->SetFormat(Ruler::RealLogFormat); + vruler->SetFormat(RealLogFormat); vruler->SetLabelEdges(true); - vruler->SetLog(false); + vruler->SetUpdater(std::make_unique()); } vruler->GetMaxSize( &wt->vrulerSize.first, &wt->vrulerSize.second ); } diff --git a/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp b/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp index d7a3a0c88a0f..ff7be16e0fb6 100644 --- a/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp +++ b/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp @@ -25,6 +25,8 @@ Paul Licameli split from TrackPanel.cpp #include "../../../TrackPanelMouseEvent.h" #include "../../../UIHandle.h" #include "../../../widgets/Ruler.h" +#include "../../../widgets/LinearUpdater.h" +#include "../../../widgets/LogarithmicUpdater.h" TimeTrackVRulerControls::~TimeTrackVRulerControls() { @@ -117,10 +119,13 @@ void TimeTrackVRulerControls::UpdateRuler( const wxRect &rect ) vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height-1); vruler->SetOrientation(wxVERTICAL); vruler->SetRange(max, min); - vruler->SetFormat((tt->GetDisplayLog()) ? Ruler::RealLogFormat : Ruler::RealFormat); + vruler->SetFormat((tt->GetDisplayLog()) ? RealLogFormat : RealFormat); vruler->SetUnits({}); vruler->SetLabelEdges(false); - vruler->SetLog(tt->GetDisplayLog()); + if (tt->GetDisplayLog()) + vruler->SetUpdater(std::make_unique()); + else + vruler->SetUpdater(std::make_unique()); vruler->GetMaxSize( &tt->vrulerSize.first, &tt->vrulerSize.second ); } diff --git a/src/widgets/CustomUpdater.cpp b/src/widgets/CustomUpdater.cpp new file mode 100644 index 000000000000..638f89e309ae --- /dev/null +++ b/src/widgets/CustomUpdater.cpp @@ -0,0 +1,43 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + + +#include "CustomUpdater.h" + +void CustomUpdater::Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context) const +{ + const int mLength = context.mLength; + const RulerStruct::Fonts& mFonts = *context.mpFonts; + + TickOutputs majorOutputs{ + allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; + int numMajorLabel = allOutputs.majorLabels.size(); + for (int i = 0; (i < numMajorLabel) && (i <= mLength); ++i) + TickCustom(dc, i, mFonts.major, majorOutputs, context); + + + TickOutputs minorOutputs{ + allOutputs.minorLabels, allOutputs.bits, allOutputs.box }; + int numMinorLabel = allOutputs.minorLabels.size(); + for (int i = 0; (i < numMinorLabel) && (i <= mLength); ++i) + TickCustom(dc, i, mFonts.minor, minorOutputs, context); + + TickOutputs minorMinorOutputs{ + allOutputs.minorMinorLabels, allOutputs.bits, allOutputs.box }; + int numMinorMinorLabel = allOutputs.minorMinorLabels.size(); + for (int i = 0; (i < numMinorMinorLabel) && (i <= mLength); ++i) + TickCustom(dc, i, mFonts.minorMinor, minorMinorOutputs, context); + + BoxAdjust(allOutputs, context); +} + +CustomUpdater::~CustomUpdater() = default; diff --git a/src/widgets/CustomUpdater.h b/src/widgets/CustomUpdater.h new file mode 100644 index 000000000000..d7243f0faccc --- /dev/null +++ b/src/widgets/CustomUpdater.h @@ -0,0 +1,31 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_CUSTOM_UPDATER__ +#define __AUDACITY_CUSTOM_UPDATER__ + +#include "Updater.h" + +struct CustomUpdater : public Updater { + using Updater::Updater; + virtual ~CustomUpdater() override = 0; + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context + ) const override; + + virtual bool TickCustom(wxDC& dc, int labelIdx, wxFont font, + TickOutputs outputs, + const RulerStruct& context + ) const = 0; +}; + +#endif diff --git a/src/widgets/CustomUpdaterPosition.cpp b/src/widgets/CustomUpdaterPosition.cpp new file mode 100644 index 000000000000..c7ad617138df --- /dev/null +++ b/src/widgets/CustomUpdaterPosition.cpp @@ -0,0 +1,56 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdaterPosition.cpp + + Dominic Mazzoni + +**********************************************************************/ + + +#include "CustomUpdaterPosition.h" + +bool CustomUpdaterPosition::TickCustom(wxDC& dc, int labelIdx, wxFont font, + // in/out: + TickOutputs outputs, + const RulerStruct& context) const +{ + const int mLeft = context.mLeft; + const int mTop = context.mTop; + const int mOrientation = context.mOrientation; + + const RulerStruct::Fonts& mFonts = *context.mpFonts; + const int mSpacing = context.mSpacing; + const bool mFlip = context.mFlip; + const TranslatableString mUnits = context.mUnits; + + // FIXME: We don't draw a tick if of end of our label arrays + // But we shouldn't have an array of labels. + if (labelIdx >= outputs.labels.size()) + return false; + + Label lab; + + lab.value = 0.0; + lab.pos = outputs.labels[labelIdx].pos; + // Custom is flexible with text format + // We can assume they use the right format, but still append the right units. + lab.text = outputs.labels[labelIdx].text; + lab.units = mUnits; + + const auto result = MakeTick( + lab, + dc, font, + outputs.bits, + mLeft, mTop, mSpacing, mFonts.lead, + mFlip, + mOrientation); + + auto& rect = result.first; + outputs.box.Union(rect); + outputs.labels[labelIdx] = (result.second); + return !rect.IsEmpty(); +} + +CustomUpdaterPosition::~CustomUpdaterPosition() = default; diff --git a/src/widgets/CustomUpdaterPosition.h b/src/widgets/CustomUpdaterPosition.h new file mode 100644 index 000000000000..e632213f9f7b --- /dev/null +++ b/src/widgets/CustomUpdaterPosition.h @@ -0,0 +1,26 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdaterPosition.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_CUSTOM_UPDATER_POSITION__ +#define __AUDACITY_CUSTOM_UPDATER_POSITION__ + +#include "CustomUpdater.h" + +struct CustomUpdaterPosition final : public CustomUpdater { + using CustomUpdater::CustomUpdater; + ~CustomUpdaterPosition() override; + + bool TickCustom(wxDC& dc, int labelIdx, wxFont font, + TickOutputs outputs, + const RulerStruct& context + ) const override; +}; + +#endif diff --git a/src/widgets/CustomUpdaterValue.cpp b/src/widgets/CustomUpdaterValue.cpp new file mode 100644 index 000000000000..b95958c50d95 --- /dev/null +++ b/src/widgets/CustomUpdaterValue.cpp @@ -0,0 +1,91 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + + +#include "CustomUpdaterValue.h" + +bool CustomUpdaterValue::TickCustom(wxDC& dc, int labelIdx, wxFont font, + // in/out: + TickOutputs outputs, + + const RulerStruct& context) const +{ + const double mMin = context.mMin; + const double mMax = context.mMax; + + const int mLeft = context.mLeft; + const int mTop = context.mTop; + const int mRight = context.mRight; + const int mBottom = context.mBottom; + const int mLength = context.mLength; + const int mOrientation = context.mOrientation; + + const RulerStruct::Fonts& mFonts = *context.mpFonts; + const int mSpacing = context.mSpacing; + const bool mFlip = context.mFlip; + const TranslatableString mUnits = context.mUnits; + const bool mLabelEdges = context.mLabelEdges; + + auto TickAtValue = + [this, &dc, &mFonts, mOrientation, mLabelEdges, + mMin, mMax, mLength, mRight, mBottom, &context] + (double value, double *pos) -> bool { + // Make a tick only if the value is strictly between the bounds + double min = std::min(mMin, mMax); + double max = std::max(mMin, mMax); + if ((value <= min && !mLabelEdges) || value < min) + return false; + if ((value >= max && !mLabelEdges) || value > max) + return false; + + int mid = (int)(mLength * ((mMin - value) / (mMin - mMax)) + 0.5); + + const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5; + if (mid >= 0 && mid < iMaxPos) { + *pos = mid; + return true; + } + return false; + }; + + // FIXME: We don't draw a tick if of end of our label arrays + // But we shouldn't have an array of labels. + if (labelIdx >= outputs.labels.size()) + return false; + + // Get the correct position based on value. + // Don't draw if the value is out of bounds. + double pos; + if (!TickAtValue(outputs.labels[labelIdx].value, &pos)) + return false; + + Label lab; + lab.value = 0.0; + lab.pos = pos; + // Custom is flexible with text format + // We can assume they use the right format, but still append the right units. + lab.text = outputs.labels[labelIdx].text; + lab.units = mUnits; + + const auto result = MakeTick( + lab, + dc, font, + outputs.bits, + mLeft, mTop, mSpacing, mFonts.lead, + mFlip, + mOrientation); + + auto& rect = result.first; + outputs.box.Union(rect); + outputs.labels[labelIdx] = (result.second); + return !rect.IsEmpty(); +} + +CustomUpdaterValue::~CustomUpdaterValue() = default; diff --git a/src/widgets/CustomUpdaterValue.h b/src/widgets/CustomUpdaterValue.h new file mode 100644 index 000000000000..c87f37e63329 --- /dev/null +++ b/src/widgets/CustomUpdaterValue.h @@ -0,0 +1,26 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdaterPosition.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_CUSTOM_UPDATER_VALUE__ +#define __AUDACITY_CUSTOM_UPDATER_VALUE__ + +#include "CustomUpdater.h" + +struct CustomUpdaterValue final : public CustomUpdater { + using CustomUpdater::CustomUpdater; + ~CustomUpdaterValue() override; + + bool TickCustom(wxDC& dc, int labelIdx, wxFont font, + TickOutputs outputs, + const RulerStruct& context + ) const override; +}; + +#endif diff --git a/src/widgets/LinearUpdater.cpp b/src/widgets/LinearUpdater.cpp new file mode 100644 index 000000000000..f083e780af23 --- /dev/null +++ b/src/widgets/LinearUpdater.cpp @@ -0,0 +1,185 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LinearUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + +#include "LinearUpdater.h" + +void LinearUpdater::Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context) const +{ + TickOutputs majorOutputs{ + allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; + + const double mDbMirrorValue = context.mDbMirrorValue; + const int mLength = context.mLength; + const RulerFormat mFormat = context.mFormat; + + const int mLeft = context.mLeft; + const int mTop = context.mTop; + const int mBottom = context.mBottom; + const int mRight = context.mRight; + const int mOrientation = context.mOrientation; + + const double mMin = context.mMin; + const double mMax = context.mMax; + const double mHiddenMin = context.mHiddenMin; + const double mHiddenMax = context.mHiddenMax; + + const RulerStruct::Fonts& mFonts = *context.mpFonts; + const bool mLabelEdges = context.mLabelEdges; + const int mLeftOffset = context.mLeftOffset; + + // Use the "hidden" min and max to determine the tick size. + // That may make a difference with fisheye. + // Otherwise you may see the tick size for the whole ruler change + // when the fisheye approaches start or end. + double UPP = (mHiddenMax - mHiddenMin) / mLength; // Units per pixel + TickSizes tickSizes{ UPP, mOrientation, mFormat, false }; + + auto TickAtValue = + [this, &tickSizes, &dc, &majorOutputs, &mFonts, mOrientation, + mMin, mMax, mLength, mLeftOffset, mRight, mBottom, &context] + (double value) -> int { + // Make a tick only if the value is strictly between the bounds + if (value < std::min(mMin, mMax)) + return -1; + if (value > std::max(mMin, mMax)) + return -1; + + int mid; + if (zoomInfo) { + // Tick only at zero + if (value) + return -1; + mid = (int)(zoomInfo->TimeToPosition(0.0, mLeftOffset)); + } + else + mid = (int)(mLength * ((mMin - value) / (mMin - mMax)) + 0.5); + + const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5; + if (mid >= 0 && mid < iMaxPos) + Tick(dc, mid, value, tickSizes, mFonts.major, majorOutputs, context); + else + return -1; + + return mid; + }; + + if (mDbMirrorValue) { + // For dB scale, let the zeroes prevail over the extreme values if + // not the same, and let midline prevail over all + + // Do the midline + TickAtValue(-mDbMirrorValue); + + // Do the upper zero + TickAtValue(0.0); + + // Do the other zero + TickAtValue(-2 * mDbMirrorValue); + } + + // Extreme values + if (mLabelEdges) { + Tick(dc, 0, mMin, tickSizes, mFonts.major, majorOutputs, context); + Tick(dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs, context); + } + + if (!mDbMirrorValue) { + // Zero (if it's strictly in the middle somewhere) + TickAtValue(0.0); + } + + double sign = UPP > 0.0 ? 1.0 : -1.0; + + int nDroppedMinorLabels = 0; + // Major and minor ticks + for (int jj = 0; jj < 2; ++jj) { + const double denom = jj == 0 ? tickSizes.mMajor : tickSizes.mMinor; + auto font = jj == 0 ? mFonts.major : mFonts.minor; + TickOutputs outputs{ + (jj == 0 ? allOutputs.majorLabels : allOutputs.minorLabels), + allOutputs.bits, allOutputs.box + }; + int ii = -1, j = 0; + double d, warpedD, nextD; + + double prevTime = 0.0, time = 0.0; + if (zoomInfo) { + j = zoomInfo->TimeToPosition(mMin); + prevTime = zoomInfo->PositionToTime(--j); + time = zoomInfo->PositionToTime(++j); + d = (prevTime + time) / 2.0; + } + else + d = mMin - UPP / 2; + if (envelope) + warpedD = ComputeWarpedLength(*envelope, 0.0, d); + else + warpedD = d; + // using ints doesn't work, as + // this will overflow and be negative at high zoom. + double step = floor(sign * warpedD / denom); + while (ii <= mLength) { + ii++; + if (zoomInfo) + { + prevTime = time; + time = zoomInfo->PositionToTime(++j); + nextD = (prevTime + time) / 2.0; + // wxASSERT(time >= prevTime); + } + else + nextD = d + UPP; + if (envelope) + warpedD += ComputeWarpedLength(*envelope, d, nextD); + else + warpedD = nextD; + d = nextD; + + if (floor(sign * warpedD / denom) > step) { + step = floor(sign * warpedD / denom); + bool major = jj == 0; + tickSizes.useMajor = major; + bool ticked = Tick(dc, ii, sign * step * denom, tickSizes, + font, outputs, context); + if (!major && !ticked) { + nDroppedMinorLabels++; + } + } + } + } + + tickSizes.useMajor = true; + + // If we've dropped minor labels through overcrowding, then don't show + // any of them. We're allowed though to drop ones which correspond to the + // major numbers. + if (nDroppedMinorLabels > + (allOutputs.majorLabels.size() + (mLabelEdges ? 2 : 0))) { + // Old code dropped the labels AND their ticks, like so: + // mMinorLabels.clear(); + // Nowadays we just drop the labels. + for (auto& label : allOutputs.minorLabels) { + label.text = {}; + label.units = {}; + } + } + + // Left and Right Edges + if (mLabelEdges) { + Tick(dc, 0, mMin, tickSizes, mFonts.major, majorOutputs, context); + Tick(dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs, context); + } + + BoxAdjust(allOutputs, context); +} + +LinearUpdater::~LinearUpdater() = default; diff --git a/src/widgets/LinearUpdater.h b/src/widgets/LinearUpdater.h new file mode 100644 index 000000000000..1c7456dd687d --- /dev/null +++ b/src/widgets/LinearUpdater.h @@ -0,0 +1,26 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LinearUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_LINEAR_UPDATER__ +#define __AUDACITY_LINEAR_UPDATER__ + +#include "Updater.h" + +struct LinearUpdater final : public Updater { + using Updater::Updater; + ~LinearUpdater() override; + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context + ) const override; +}; + +#endif diff --git a/src/widgets/LogarithmicUpdater.cpp b/src/widgets/LogarithmicUpdater.cpp new file mode 100644 index 000000000000..0a1bc58d189c --- /dev/null +++ b/src/widgets/LogarithmicUpdater.cpp @@ -0,0 +1,124 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LogarithmicUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + +#include "LogarithmicUpdater.h" + +void LogarithmicUpdater::Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context) const +{ + TickOutputs majorOutputs{ + allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; + + const int mLength = context.mLength; + const RulerFormat mFormat = context.mFormat; + + const int mOrientation = context.mOrientation; + + const double mMin = context.mMin; + const double mMax = context.mMax; + const double mHiddenMin = context.mHiddenMin; + const double mHiddenMax = context.mHiddenMax; + + const RulerStruct::Fonts& mFonts = *context.mpFonts; + const NumberScale mNumberScale = context.mNumberScale; + + auto numberScale = (mNumberScale == NumberScale{}) + ? NumberScale(nstLogarithmic, mMin, mMax) + : mNumberScale; + + double UPP = (mHiddenMax - mHiddenMin) / mLength; // Units per pixel + TickSizes tickSizes{ UPP, mOrientation, mFormat, true }; + + tickSizes.mDigits = 2; //TODO: implement dynamic digit computation + + double loLog = log10(mMin); + double hiLog = log10(mMax); + int loDecade = (int)floor(loLog); + + double val; + double startDecade = pow(10., (double)loDecade); + + // Major ticks are the decades + double decade = startDecade; + double delta = hiLog - loLog, steps = fabs(delta); + double step = delta >= 0 ? 10 : 0.1; + double rMin = std::min(mMin, mMax), rMax = std::max(mMin, mMax); + for (int i = 0; i <= steps; i++) + { // if(i!=0) + { val = decade; + if (val >= rMin && val < rMax) { + const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); + Tick(dc, pos, val, tickSizes, mFonts.major, majorOutputs, context); + } + } + decade *= step; + } + + // Minor ticks are multiples of decades + decade = startDecade; + float start, end, mstep; + if (delta > 0) + { + start = 2; end = 10; mstep = 1; + } + else + { + start = 9; end = 1; mstep = -1; + } + steps++; + tickSizes.useMajor = false; + TickOutputs minorOutputs{ + allOutputs.minorLabels, allOutputs.bits, allOutputs.box }; + for (int i = 0; i <= steps; i++) { + for (int j = start; j != end; j += mstep) { + val = decade * j; + if (val >= rMin && val < rMax) { + const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); + Tick(dc, pos, val, tickSizes, mFonts.minor, minorOutputs, context); + } + } + decade *= step; + } + + // MinorMinor ticks are multiples of decades + decade = startDecade; + if (delta > 0) + { + start = 10; end = 100; mstep = 1; + } + else + { + start = 100; end = 10; mstep = -1; + } + steps++; + TickOutputs minorMinorOutputs{ + allOutputs.minorMinorLabels, allOutputs.bits, allOutputs.box }; + for (int i = 0; i <= steps; i++) { + // PRL: Bug1038. Don't label 1.6, rounded, as a duplicate tick for "2" + if (!(mFormat == IntFormat && decade < 10.0)) { + for (int f = start; f != (int)(end); f += mstep) { + if ((int)(f / 10) != f / 10.0f) { + val = decade * f / 10; + if (val >= rMin && val < rMax) { + const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); + Tick(dc, pos, val, tickSizes, + mFonts.minorMinor, minorMinorOutputs, context); + } + } + } + } + decade *= step; + } + + BoxAdjust(allOutputs, context); +} + +LogarithmicUpdater::~LogarithmicUpdater() = default; diff --git a/src/widgets/LogarithmicUpdater.h b/src/widgets/LogarithmicUpdater.h new file mode 100644 index 000000000000..d2b223a110cc --- /dev/null +++ b/src/widgets/LogarithmicUpdater.h @@ -0,0 +1,27 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LogarithmicUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_LOGARITHMIC_UPDATER__ +#define __AUDACITY_LOGARITHMIC_UPDATER__ + +#include "Updater.h" + +struct LogarithmicUpdater final : public Updater { + using Updater::Updater; + ~LogarithmicUpdater() override; + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs, const RulerStruct& context + ) const override; +}; + +#endif + diff --git a/src/widgets/MeterPanel.cpp b/src/widgets/MeterPanel.cpp index 01d4ea9eb512..4da9373242f9 100644 --- a/src/widgets/MeterPanel.cpp +++ b/src/widgets/MeterPanel.cpp @@ -1264,7 +1264,7 @@ void MeterPanel::SetActiveStyle(Style newStyle) if (mDB) { - mRuler.SetFormat(Ruler::LinearDBFormat); + mRuler.SetFormat(LinearDBFormat); if (mStyle == HorizontalStereo || mStyle == HorizontalStereoCompact) { mRuler.SetOrientation(wxHORIZONTAL); @@ -1278,7 +1278,7 @@ void MeterPanel::SetActiveStyle(Style newStyle) } else { - mRuler.SetFormat(Ruler::RealFormat); + mRuler.SetFormat(RealFormat); if (mStyle == HorizontalStereo || mStyle == HorizontalStereoCompact) { mRuler.SetOrientation(wxHORIZONTAL); diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index df878074cbe0..f78945c5d5bc 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -36,21 +36,6 @@ "1:06:40", for example. Will display fractions of a second, and tick marks are all reasonable round numbers for time (i.e. 15 seconds, 30 seconds, etc.) -*//***************************************************************//** - -\class RulerPanel -\brief RulerPanel class allows you to work with a Ruler like - any other wxWindow. - -*//***************************************************************//** - - -\class Ruler::Label -\brief An array of these created by the Ruler is used to determine -what and where text annotations to the numbers on the Ruler get drawn. - -\todo Check whether Ruler is costing too much time in allocation/free of -array of Ruler::Label. *//******************************************************************/ @@ -67,6 +52,10 @@ array of Ruler::Label. #include "Theme.h" #include "ViewInfo.h" +#include "Updater.h" +// Need to include to set default +#include "LinearUpdater.h" + using std::min; using std::max; @@ -78,25 +67,12 @@ using std::max; Ruler::Ruler() { - mMin = mHiddenMin = 0.0; - mMax = mHiddenMax = 100.0; - mOrientation = wxHORIZONTAL; - mSpacing = 6; mHasSetSpacing = false; - mFormat = RealFormat; - mFlip = false; - mLog = false; - mLabelEdges = false; - - mLeft = -1; - mTop = -1; - mRight = -1; - mBottom = -1; + mbTicksOnly = true; mbTicksAtExtremes = false; mTickColour = wxColour( theTheme.Colour( clrTrackPanelText )); mPen.SetColour(mTickColour); - mDbMirrorValue = 0.0; // Note: the font size is now adjusted automatically whenever // Invalidate is called on a horizontal Ruler, unless the user @@ -108,14 +84,17 @@ Ruler::Ruler() fontSize = 8; #endif - mLength = 0; - - mCustom = false; mbMinor = true; mTwoTone = false; mUseZoomInfo = NULL; + + // This part in particular needs inspection, not giving an error + // But is this corret? And should it be set to NULL or nullptr if a default + // cannot be made? + mpUpdater = std::make_unique( mUseZoomInfo ); + // mpUpdater = nullptr; } Ruler::~Ruler() @@ -132,22 +111,38 @@ void Ruler::SetFormat(RulerFormat format) { // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat - if (mFormat != format) { - mFormat = format; + if (mRulerStruct.mFormat != format) { + mRulerStruct.mFormat = format; Invalidate(); } } -void Ruler::SetLog(bool log) +void Ruler::SetUpdater(std::unique_ptr pUpdater) { - // Logarithmic + // Should a comparison be made between mpUpdater and pUpdater? + // Runtime type comparison isn't clean in c++ + mpUpdater = std::move(pUpdater); + ResetCustomLabels(true, true, true); + Invalidate(); +} - if (mLog != log) { - mLog = log; +void Ruler::SetUpdater + (std::unique_ptr pUpdater, int leftOffset) +{ + // Should a comparison be made between mpUpdater and pUpdater? + // Runtime type comparison isn't clean in c++ + mpUpdater = std::move(pUpdater); - Invalidate(); - } + if (mRulerStruct.mLeftOffset != leftOffset) + mRulerStruct.mLeftOffset = leftOffset; + + // Hm, is this invalidation sufficient? What if *zoomInfo changes under us? + if (mUseZoomInfo != mpUpdater->zoomInfo) + mUseZoomInfo = mpUpdater->zoomInfo; + + ResetCustomLabels(true, true, true); + Invalidate(); } void Ruler::SetUnits(const TranslatableString &units) @@ -155,8 +150,8 @@ void Ruler::SetUnits(const TranslatableString &units) // Specify the name of the units (like "dB") if you // want numbers like "1.6" formatted as "1.6 dB". - if (mUnits != units) { - mUnits = units; + if (mRulerStruct.mUnits != units) { + mRulerStruct.mUnits = units; Invalidate(); } @@ -164,8 +159,8 @@ void Ruler::SetUnits(const TranslatableString &units) void Ruler::SetDbMirrorValue( const double d ) { - if (mDbMirrorValue != d) { - mDbMirrorValue = d; + if (mRulerStruct.mDbMirrorValue != d) { + mRulerStruct.mDbMirrorValue = d; Invalidate(); } @@ -175,11 +170,11 @@ void Ruler::SetOrientation(int orient) { // wxHORIZONTAL || wxVERTICAL - if (mOrientation != orient) { - mOrientation = orient; + if (mRulerStruct.mOrientation != orient) { + mRulerStruct.mOrientation = orient; - if (mOrientation == wxVERTICAL && !mHasSetSpacing) - mSpacing = 2; + if (mRulerStruct.mOrientation == wxVERTICAL && !mHasSetSpacing) + mRulerStruct.mSpacing = 2; Invalidate(); } @@ -201,12 +196,12 @@ void Ruler::SetRange // hiddenMin and hiddenMax are values that would be shown with the fisheye // turned off. In other cases they equal min and max respectively. - if (mMin != min || mMax != max || - mHiddenMin != hiddenMin || mHiddenMax != hiddenMax) { - mMin = min; - mMax = max; - mHiddenMin = hiddenMin; - mHiddenMax = hiddenMax; + if (mRulerStruct.mMin != min || mRulerStruct.mMax != max || + mRulerStruct.mHiddenMin != hiddenMin || mRulerStruct.mHiddenMax != hiddenMax) { + mRulerStruct.mMin = min; + mRulerStruct.mMax = max; + mRulerStruct.mHiddenMin = hiddenMin; + mRulerStruct.mHiddenMax = hiddenMax; Invalidate(); } @@ -216,8 +211,8 @@ void Ruler::SetSpacing(int spacing) { mHasSetSpacing = true; - if (mSpacing != spacing) { - mSpacing = spacing; + if (mRulerStruct.mSpacing != spacing) { + mRulerStruct.mSpacing = spacing; Invalidate(); } @@ -229,8 +224,8 @@ void Ruler::SetLabelEdges(bool labelEdges) // receive a label. If not, the nearest round number is // labeled (which may or may not be the edge). - if (mLabelEdges != labelEdges) { - mLabelEdges = labelEdges; + if (mRulerStruct.mLabelEdges != labelEdges) { + mRulerStruct.mLabelEdges = labelEdges; Invalidate(); } @@ -242,8 +237,8 @@ void Ruler::SetFlip(bool flip) // is reversed from the default; eg. above the line // instead of below - if (mFlip != flip) { - mFlip = flip; + if (mRulerStruct.mFlip != flip) { + mRulerStruct.mFlip = flip; Invalidate(); } @@ -279,39 +274,39 @@ void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxF { // Won't override these fonts - mpUserFonts = std::make_unique( - Fonts{ majorFont, minorFont, minorMinorFont, 0 } ); + mpUserFonts = std::make_unique( + RulerStruct::Fonts{ majorFont, minorFont, minorMinorFont, 0 } ); wxScreenDC dc; wxCoord height; FindFontHeights( height, mpUserFonts->lead, dc, majorFont ); - mpFonts.reset(); - mpFonts.reset(); + mRulerStruct.mpFonts.reset(); + mRulerStruct.mpFonts.reset(); Invalidate(); } void Ruler::SetNumberScale(const NumberScale &scale) { - if ( mNumberScale != scale ) { - mNumberScale = scale; + if ( mRulerStruct.mNumberScale != scale ) { + mRulerStruct.mNumberScale = scale; Invalidate(); } } void Ruler::OfflimitsPixels(int start, int end) { - int length = mLength; - if (mOrientation == wxHORIZONTAL) - length = mRight - mLeft; + int length = mRulerStruct.mLength; + if (mRulerStruct.mOrientation == wxHORIZONTAL) + length = mRulerStruct.mRight - mRulerStruct.mLeft; else - length = mBottom - mTop; + length = mRulerStruct.mBottom - mRulerStruct.mTop; if( length < 0 ) return; auto size = static_cast( length + 1 ); if ( mUserBits.size() < size ) { - mLength = length; + mRulerStruct.mLength = length; mUserBits.resize( size, false ); } @@ -320,8 +315,8 @@ void Ruler::OfflimitsPixels(int start, int end) if (start < 0) start = 0; - if (end > mLength) - end = mLength; + if (end > mRulerStruct.mLength) + end = mRulerStruct.mLength; for(int i = start; i <= end; i++) mUserBits[i] = true; @@ -331,12 +326,12 @@ void Ruler::OfflimitsPixels(int start, int end) void Ruler::SetBounds(int left, int top, int right, int bottom) { - if (mLeft != left || mTop != top || - mRight != right || mBottom != bottom) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; + if (mRulerStruct.mLeft != left || mRulerStruct.mTop != top || + mRulerStruct.mRight != right || mRulerStruct.mBottom != bottom) { + mRulerStruct.mLeft = left; + mRulerStruct.mTop = top; + mRulerStruct.mRight = right; + mRulerStruct.mBottom = bottom; Invalidate(); } @@ -344,659 +339,22 @@ void Ruler::SetBounds(int left, int top, int right, int bottom) void Ruler::Invalidate() { - if (mOrientation == wxHORIZONTAL) - mLength = mRight-mLeft; + if (mRulerStruct.mOrientation == wxHORIZONTAL) + mRulerStruct.mLength = mRulerStruct.mRight-mRulerStruct.mLeft; else - mLength = mBottom-mTop; + mRulerStruct.mLength = mRulerStruct.mBottom-mRulerStruct.mTop; mpCache.reset(); // Bug 2316 we must preserve off-limit pixels. // mUserBits.clear(); } -struct Ruler::TickSizes -{ - bool useMajor = true; - - double mMajor; - double mMinor; - - int mDigits; - -TickSizes( double UPP, int orientation, RulerFormat format, bool log ) -{ - //TODO: better dynamic digit computation for the log case - (void)log; - - // Given the dimensions of the ruler, the range of values it - // has to display, and the format (i.e. Int, Real, Time), - // figure out how many units are in one Minor tick, and - // in one Major tick. - // - // The goal is to always put tick marks on nice round numbers - // that are easy for humans to grok. This is the most tricky - // with time. - - double d; - - // As a heuristic, we want at least 22 pixels between each - // minor tick. We want to show numbers like "-48" - // in that space. - // If vertical, we don't need as much space. - double units = ((orientation == wxHORIZONTAL) ? 22 : 16) * fabs(UPP); - - mDigits = 0; - - switch(format) { - case LinearDBFormat: - if (units < 0.001) { - mMinor = 0.001; - mMajor = 0.005; - return; - } - if (units < 0.01) { - mMinor = 0.01; - mMajor = 0.05; - return; - } - if (units < 0.1) { - mMinor = 0.1; - mMajor = 0.5; - return; - } - if (units < 1.0) { - mMinor = 1.0; - mMajor = 6.0; - return; - } - if (units < 3.0) { - mMinor = 3.0; - mMajor = 12.0; - return; - } - if (units < 6.0) { - mMinor = 6.0; - mMajor = 24.0; - return; - } - if (units < 12.0) { - mMinor = 12.0; - mMajor = 48.0; - return; - } - if (units < 24.0) { - mMinor = 24.0; - mMajor = 96.0; - return; - } - d = 20.0; - for(;;) { - if (units < d) { - mMinor = d; - mMajor = d*5.0; - return; - } - d *= 5.0; - if (units < d) { - mMinor = d; - mMajor = d*5.0; - return; - } - d *= 2.0; - } - break; - - case IntFormat: - d = 1.0; - for(;;) { - if (units < d) { - mMinor = d; - mMajor = d*5.0; - return; - } - d *= 5.0; - if (units < d) { - mMinor = d; - mMajor = d*2.0; - return; - } - d *= 2.0; - } - break; - - case TimeFormat: - if (units > 0.5) { - if (units < 1.0) { // 1 sec - mMinor = 1.0; - mMajor = 5.0; - return; - } - if (units < 5.0) { // 5 sec - mMinor = 5.0; - mMajor = 15.0; - return; - } - if (units < 10.0) { - mMinor = 10.0; - mMajor = 30.0; - return; - } - if (units < 15.0) { - mMinor = 15.0; - mMajor = 60.0; - return; - } - if (units < 30.0) { - mMinor = 30.0; - mMajor = 60.0; - return; - } - if (units < 60.0) { // 1 min - mMinor = 60.0; - mMajor = 300.0; - return; - } - if (units < 300.0) { // 5 min - mMinor = 300.0; - mMajor = 900.0; - return; - } - if (units < 600.0) { // 10 min - mMinor = 600.0; - mMajor = 1800.0; - return; - } - if (units < 900.0) { // 15 min - mMinor = 900.0; - mMajor = 3600.0; - return; - } - if (units < 1800.0) { // 30 min - mMinor = 1800.0; - mMajor = 3600.0; - return; - } - if (units < 3600.0) { // 1 hr - mMinor = 3600.0; - mMajor = 6*3600.0; - return; - } - if (units < 6*3600.0) { // 6 hrs - mMinor = 6*3600.0; - mMajor = 24*3600.0; - return; - } - if (units < 24*3600.0) { // 1 day - mMinor = 24*3600.0; - mMajor = 7*24*3600.0; - return; - } - - mMinor = 24.0 * 7.0 * 3600.0; // 1 week - mMajor = 24.0 * 7.0 * 3600.0; - } - - // Otherwise fall through to RealFormat - // (fractions of a second should be dealt with - // the same way as for RealFormat) - - case RealFormat: - d = 0.000001; - // mDigits is number of digits after the decimal point. - mDigits = 6; - for(;;) { - if (units < d) { - mMinor = d; - mMajor = d*5.0; - return; - } - d *= 5.0; - if (units < d) { - mMinor = d; - mMajor = d*2.0; - return; - } - d *= 2.0; - mDigits--; - // More than 10 digit numbers? Something is badly wrong. - // Probably units is coming in with too high a value. - wxASSERT( mDigits >= -10 ); - if( mDigits < -10 ) - break; - } - mMinor = d; - mMajor = d * 2.0; - break; - - case RealLogFormat: - d = 0.000001; - // mDigits is number of digits after the decimal point. - mDigits = 6; - for(;;) { - if (units < d) { - mMinor = d; - mMajor = d*5.0; - return; - } - d *= 5.0; - if (units < d) { - mMinor = d; - mMajor = d*2.0; - return; - } - d *= 2.0; - mDigits--; - // More than 10 digit numbers? Something is badly wrong. - // Probably units is coming in with too high a value. - wxASSERT( mDigits >= -10 ); - if( mDigits < -10 ) - break; - } - mDigits++; - mMinor = d; - mMajor = d * 2.0; - break; - } -} - -TranslatableString LabelString( - double d, RulerFormat format, const TranslatableString &units ) - const -{ - // Given a value, turn it into a string according - // to the current ruler format. The number of digits of - // accuracy depends on the resolution of the ruler, - // i.e. how far zoomed in or out you are. - - wxString s; - - // PRL Todo: are all these cases properly localized? (Decimal points, - // hour-minute-second, etc.?) - - // Replace -0 with 0 - if (d < 0.0 && (d+mMinor > 0.0) && ( format != RealLogFormat )) - d = 0.0; - - switch( format ) { - case IntFormat: - s.Printf(wxT("%d"), (int)floor(d+0.5)); - break; - case LinearDBFormat: - if (mMinor >= 1.0) - s.Printf(wxT("%d"), (int)floor(d+0.5)); - else { - int precision = -log10(mMinor); - s.Printf(wxT("%.*f"), precision, d); - } - break; - case RealFormat: - if (mMinor >= 1.0) - s.Printf(wxT("%d"), (int)floor(d+0.5)); - else { - s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d); - } - break; - case RealLogFormat: - if (mMinor >= 1.0) - s.Printf(wxT("%d"), (int)floor(d+0.5)); - else { - s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d); - } - break; - case TimeFormat: - if (useMajor) { - if (d < 0) { - s = wxT("-"); - d = -d; - } - - #if ALWAYS_HH_MM_SS - int secs = (int)(d + 0.5); - if (mMinor >= 1.0) { - s.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60); - } - else { - wxString t1, t2, format; - t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60); - format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits); - t2.Printf(format, fmod(d, 60.0)); - s += t1 + t2; - } - break; - #endif - - if (mMinor >= 3600.0) { - int hrs = (int)(d / 3600.0 + 0.5); - wxString h; - h.Printf(wxT("%d:00:00"), hrs); - s += h; - } - else if (mMinor >= 60.0) { - int minutes = (int)(d / 60.0 + 0.5); - wxString m; - if (minutes >= 60) - m.Printf(wxT("%d:%02d:00"), minutes/60, minutes%60); - else - m.Printf(wxT("%d:00"), minutes); - s += m; - } - else if (mMinor >= 1.0) { - int secs = (int)(d + 0.5); - wxString t; - if (secs >= 3600) - t.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60); - else if (secs >= 60) - t.Printf(wxT("%d:%02d"), secs/60, secs%60); - else - t.Printf(wxT("%d"), secs); - s += t; - } - else { -// Commented out old and incorrect code for avoiding the 40mins and 60 seconds problem -// It was causing Bug 463 - Incorrect Timeline numbering (where at high zoom and long tracks, -// numbers did not change. -#if 0 - // The casting to float is working around an issue where 59 seconds - // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3. - int secs = (int)(float)(d); - wxString t1, t2, format; - - if (secs >= 3600) - t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60); - else if (secs >= 60) - t1.Printf(wxT("%d:"), secs/60); - - if (secs >= 60) - format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits); - else - format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits); - // The casting to float is working around an issue where 59 seconds - // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3. - t2.Printf(format, fmod((float)d, (float)60.0)); -#else - // For d in the range of hours, d is just very slightly below the value it should - // have, because of using a double, which in turn yields values like 59:59:999999 - // mins:secs:nanosecs when we want 1:00:00:000000 - // so adjust by less than a nano second per hour to get nicer number formatting. - double dd = d * 1.000000000000001; - int secs = (int)(dd); - wxString t1, t2, format; - - if (secs >= 3600) - t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60); - else if (secs >= 60) - t1.Printf(wxT("%d:"), secs/60); - - if (secs >= 60) - format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits); - else - format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits); - // dd will be reduced to just the seconds and fractional part. - dd = dd - secs + (secs%60); - // truncate to appropriate number of digits, so that the print formatting - // doesn't round up 59.9999999 to 60. - double multiplier = pow( 10, mDigits); - dd = ((int)(dd * multiplier))/multiplier; - t2.Printf(format, dd); -#endif - s += t1 + t2; - } - } - else { - } - } - - auto result = Verbatim( s ); - if (!units.empty()) - result += units; - - return result; -} - -}; // struct Ruler::TickSizes - -auto Ruler::MakeTick( - Label lab, - wxDC &dc, wxFont font, - std::vector &bits, - int left, int top, int spacing, int lead, - bool flip, int orientation ) - -> std::pair< wxRect, Label > -{ - lab.lx = left - 1000; // don't display - lab.ly = top - 1000; // don't display - - auto length = bits.size() - 1; - auto pos = lab.pos; - - dc.SetFont( font ); - - wxCoord strW, strH, strD, strL; - auto str = lab.text; - // Do not put the text into results until we are sure it does not overlap - lab.text = {}; - dc.GetTextExtent(str.Translation(), &strW, &strH, &strD, &strL); - - int strPos, strLen, strLeft, strTop; - if ( orientation == wxHORIZONTAL ) { - strLen = strW; - strPos = pos - strW/2; - if (strPos < 0) - strPos = 0; - if (strPos + strW >= length) - strPos = length - strW; - strLeft = left + strPos; - if ( flip ) - strTop = top + 4; - else - strTop = -strH - lead; -// strTop = top - lead + 4;// More space was needed... - } - else { - strLen = strH; - strPos = pos - strH/2; - if (strPos < 0) - strPos = 0; - if (strPos + strH >= length) - strPos = length - strH; - strTop = top + strPos; - if ( flip ) - strLeft = left + 5; - else - strLeft = -strW - 6; - } - - // FIXME: we shouldn't even get here if strPos < 0. - // Ruler code currently does not handle very small or - // negative sized windows (i.e. don't draw) properly. - if( strPos < 0 ) - return { {}, lab }; - - // See if any of the pixels we need to draw this - // label is already covered - - int i; - for(i=0; i length - spacing) - rightMargin = length - strPos - strLen; - strLen += rightMargin; - - for(i=0; i &pFonts, const Fonts *pUserFonts, - wxDC &dc, int desiredPixelHeight ); - - struct UpdateOutputs; - - void Update( - wxDC &dc, const Envelope* envelope, - UpdateOutputs &allOutputs - )// Envelope *speedEnv, long minSpeed, long maxSpeed ) - const; - - void UpdateCustom( wxDC &dc, UpdateOutputs &allOutputs ) const; - void UpdateLinear( - wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs ) const; - void UpdateNonlinear( wxDC &dc, UpdateOutputs &allOutputs ) const; -}; - struct Ruler::Cache { - Bits mBits; - Labels mMajorLabels, mMinorLabels, mMinorMinorLabels; + Updater::Bits mBits; + Updater::Labels mMajorLabels, mMinorLabels, mMinorMinorLabels; wxRect mRect; }; -struct Ruler::Updater::TickOutputs{ Labels &labels; Bits &bits; wxRect &box; }; -struct Ruler::Updater::UpdateOutputs { - Labels &majorLabels, &minorLabels, &minorMinorLabels; - Bits &bits; - wxRect &box; -}; - -bool Ruler::Updater::Tick( wxDC &dc, - int pos, double d, const TickSizes &tickSizes, wxFont font, - // in/out: - TickOutputs outputs ) const -{ - // Bug 521. dB view for waveforms needs a 2-sided scale. - if(( mDbMirrorValue > 1.0 ) && ( -d > mDbMirrorValue )) - d = -2*mDbMirrorValue - d; - - // FIXME: We don't draw a tick if off end of our label arrays - // But we shouldn't have an array of labels. - if( outputs.labels.size() >= mLength ) - return false; - - Label lab; - lab.value = d; - lab.pos = pos; - lab.text = tickSizes.LabelString( d, mFormat, mUnits ); - - const auto result = MakeTick( - lab, - dc, font, - outputs.bits, - mLeft, mTop, mSpacing, mFonts.lead, - mFlip, - mOrientation ); - - auto &rect = result.first; - outputs.box.Union( rect ); - outputs.labels.emplace_back( result.second ); - return !rect.IsEmpty(); -} - -bool Ruler::Updater::TickCustom( wxDC &dc, int labelIdx, wxFont font, - // in/out: - TickOutputs outputs ) const -{ - // FIXME: We don't draw a tick if of end of our label arrays - // But we shouldn't have an array of labels. - if( labelIdx >= outputs.labels.size() ) - return false; - - //This should only used in the mCustom case - - Label lab; - lab.value = 0.0; - - const auto result = MakeTick( - lab, - - dc, font, - outputs.bits, - mLeft, mTop, mSpacing, mFonts.lead, - mFlip, - mOrientation ); - - auto &rect = result.first; - outputs.box.Union( rect ); - outputs.labels[labelIdx] = ( result.second ); - return !rect.IsEmpty(); -} - -namespace { -double ComputeWarpedLength(const Envelope &env, double t0, double t1) -{ - return env.IntegralOfInverse(t0, t1); -} - -double SolveWarpedLength(const Envelope &env, double t0, double length) -{ - return env.SolveIntegralOfInverse(t0, length); -} -} - static constexpr int MinPixelHeight = #ifdef __WXMSW__ 12; @@ -1014,20 +372,23 @@ static constexpr int MaxPixelHeight = #endif -void Ruler::Updater::ChooseFonts( - std::unique_ptr &pFonts, const Fonts *pUserFonts, - wxDC &dc, int desiredPixelHeight ) +void Ruler::ChooseFonts( wxDC &dc ) const { - if ( pFonts ) + const RulerStruct::Fonts* pUserFonts = mpUserFonts.get(); + int desiredPixelHeight = mRulerStruct.mOrientation == wxHORIZONTAL + ? mRulerStruct.mBottom - mRulerStruct.mTop - 5 // height less ticks and 1px gap + : MaxPixelHeight; + + if (mRulerStruct.mpFonts) return; - if ( pUserFonts ) { - pFonts = std::make_unique( *pUserFonts ); + if (pUserFonts) { + mRulerStruct.mpFonts = std::make_unique(*pUserFonts); return; } - pFonts = std::make_unique( Fonts{ {}, {}, {}, 0 } ); - auto &fonts = *pFonts; + mRulerStruct.mpFonts = std::make_unique(RulerStruct::Fonts{ {}, {}, {}, 0 }); + auto& fonts = *(mRulerStruct.mpFonts); int fontSize = 4; @@ -1036,329 +397,18 @@ void Ruler::Updater::ChooseFonts( // Keep making the font bigger until it's too big, then subtract one. wxCoord height; - FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD ); + FindFontHeights(height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD); while (height <= desiredPixelHeight && fontSize < 40) { fontSize++; - FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD ); + FindFontHeights(height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD); } fontSize--; - FindFontHeights( height, fonts.lead, dc, fontSize ); + FindFontHeights(height, fonts.lead, dc, fontSize); fonts.major = wxFont{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD }; fonts.minor = wxFont{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL }; fonts.minorMinor = wxFont{ fontSize - 1, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL }; -} - -void Ruler::Updater::UpdateCustom( wxDC &dc, UpdateOutputs &allOutputs ) const -{ - TickOutputs majorOutputs{ - allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; - - // SET PARAMETER IN MCUSTOM CASE - // Works only with major labels - - int numLabel = allOutputs.majorLabels.size(); - - for( int i = 0; (i int { - // Make a tick only if the value is strictly between the bounds - if ( value <= std::min( mMin, mMax ) ) - return -1; - if ( value >= std::max( mMin, mMax ) ) - return -1; - - int mid; - if (zoomInfo != NULL) { - // Tick only at zero - if ( value ) - return -1; - mid = (int)(zoomInfo->TimeToPosition(0.0, mLeftOffset)); - } - else - mid = (int)(mLength*((mMin - value) / (mMin - mMax)) + 0.5); - - const int iMaxPos = (mOrientation == wxHORIZONTAL) ? mRight : mBottom - 5; - if (mid >= 0 && mid < iMaxPos) - Tick( dc, mid, value, tickSizes, mFonts.major, majorOutputs ); - else - return -1; - - return mid; - }; - - if ( mDbMirrorValue ) { - // For dB scale, let the zeroes prevail over the extreme values if - // not the same, and let midline prevail over all - - // Do the midline - TickAtValue( -mDbMirrorValue ); - - // Do the upper zero - TickAtValue( 0.0 ); - - // Do the other zero - TickAtValue( -2 * mDbMirrorValue ); - } - - // Extreme values - if (mLabelEdges) { - Tick( dc, 0, mMin, tickSizes, mFonts.major, majorOutputs ); - Tick( dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs ); - } - - if ( !mDbMirrorValue ) { - // Zero (if it's strictly in the middle somewhere) - TickAtValue( 0.0 ); - } - - double sg = UPP > 0.0? 1.0: -1.0; - - int nDroppedMinorLabels=0; - // Major and minor ticks - for (int jj = 0; jj < 2; ++jj) { - const double denom = jj == 0 ? tickSizes.mMajor : tickSizes.mMinor; - auto font = jj == 0 ? mFonts.major : mFonts.minor; - TickOutputs outputs{ - (jj == 0 ? allOutputs.majorLabels : allOutputs.minorLabels), - allOutputs.bits, allOutputs.box - }; - int ii = -1, j = 0; - double d, warpedD, nextD; - - double prevTime = 0.0, time = 0.0; - if (zoomInfo != NULL) { - j = zoomInfo->TimeToPosition(mMin); - prevTime = zoomInfo->PositionToTime(--j); - time = zoomInfo->PositionToTime(++j); - d = (prevTime + time) / 2.0; - } - else - d = mMin - UPP / 2; - if (envelope) - warpedD = ComputeWarpedLength(*envelope, 0.0, d); - else - warpedD = d; - // using ints doesn't work, as - // this will overflow and be negative at high zoom. - double step = floor(sg * warpedD / denom); - while (ii <= mLength) { - ii++; - if (zoomInfo) - { - prevTime = time; - time = zoomInfo->PositionToTime(++j); - nextD = (prevTime + time) / 2.0; - // wxASSERT(time >= prevTime); - } - else - nextD = d + UPP; - if (envelope) - warpedD += ComputeWarpedLength(*envelope, d, nextD); - else - warpedD = nextD; - d = nextD; - - if (floor(sg * warpedD / denom) > step) { - step = floor(sg * warpedD / denom); - bool major = jj == 0; - tickSizes.useMajor = major; - bool ticked = Tick( dc, ii, sg * step * denom, tickSizes, - font, outputs ); - if( !major && !ticked ){ - nDroppedMinorLabels++; - } - } - } - } - - tickSizes.useMajor = true; - - // If we've dropped minor labels through overcrowding, then don't show - // any of them. We're allowed though to drop ones which correspond to the - // major numbers. - if( nDroppedMinorLabels > - (allOutputs.majorLabels.size() + (mLabelEdges ? 2:0)) ){ - // Old code dropped the labels AND their ticks, like so: - // mMinorLabels.clear(); - // Nowadays we just drop the labels. - for( auto &label : allOutputs.minorLabels ) - label.text = {}; - } - - // Left and Right Edges - if (mLabelEdges) { - Tick( dc, 0, mMin, tickSizes, mFonts.major, majorOutputs ); - Tick( dc, mLength, mMax, tickSizes, mFonts.major, majorOutputs ); - } -} - -void Ruler::Updater::UpdateNonlinear( - wxDC &dc, UpdateOutputs &allOutputs ) const -{ - TickOutputs majorOutputs{ - allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; - - auto numberScale = ( mNumberScale == NumberScale{} ) - ? NumberScale( nstLogarithmic, mMin, mMax ) - : mNumberScale; - - double UPP = (mHiddenMax-mHiddenMin)/mLength; // Units per pixel - TickSizes tickSizes{ UPP, mOrientation, mFormat, true }; - - tickSizes.mDigits = 2; //TODO: implement dynamic digit computation - - double loLog = log10(mMin); - double hiLog = log10(mMax); - int loDecade = (int) floor(loLog); - - double val; - double startDecade = pow(10., (double)loDecade); - - // Major ticks are the decades - double decade = startDecade; - double delta=hiLog-loLog, steps=fabs(delta); - double step = delta>=0 ? 10 : 0.1; - double rMin=std::min(mMin, mMax), rMax=std::max(mMin, mMax); - for(int i=0; i<=steps; i++) - { // if(i!=0) - { val = decade; - if(val >= rMin && val < rMax) { - const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); - Tick( dc, pos, val, tickSizes, mFonts.major, majorOutputs ); - } - } - decade *= step; - } - - // Minor ticks are multiples of decades - decade = startDecade; - float start, end, mstep; - if (delta > 0) - { start=2; end=10; mstep=1; - }else - { start=9; end=1; mstep=-1; - } - steps++; - tickSizes.useMajor = false; - TickOutputs minorOutputs{ - allOutputs.minorLabels, allOutputs.bits, allOutputs.box }; - for(int i=0; i<=steps; i++) { - for(int j=start; j!=end; j+=mstep) { - val = decade * j; - if(val >= rMin && val < rMax) { - const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); - Tick( dc, pos, val, tickSizes, mFonts.minor, minorOutputs ); - } - } - decade *= step; - } - - // MinorMinor ticks are multiples of decades - decade = startDecade; - if (delta > 0) - { start= 10; end=100; mstep= 1; - }else - { start=100; end= 10; mstep=-1; - } - steps++; - TickOutputs minorMinorOutputs{ - allOutputs.minorMinorLabels, allOutputs.bits, allOutputs.box }; - for (int i = 0; i <= steps; i++) { - // PRL: Bug1038. Don't label 1.6, rounded, as a duplicate tick for "2" - if (!(mFormat == IntFormat && decade < 10.0)) { - for (int f = start; f != (int)(end); f += mstep) { - if ((int)(f / 10) != f / 10.0f) { - val = decade * f / 10; - if (val >= rMin && val < rMax) { - const int pos(0.5 + mLength * numberScale.ValueToPosition(val)); - Tick( dc, pos, val, tickSizes, - mFonts.minorMinor, minorMinorOutputs ); - } - } - } - } - decade *= step; - } -} - -void Ruler::Updater::Update( - wxDC &dc, const Envelope* envelope, - UpdateOutputs &allOutputs -)// Envelope *speedEnv, long minSpeed, long maxSpeed ) - const -{ - TickOutputs majorOutputs{ - allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; - - if ( mCustom ) - UpdateCustom( dc, allOutputs ); - else if ( !mLog ) - UpdateLinear( dc, envelope, allOutputs ); - else - UpdateNonlinear( dc, allOutputs ); - - int displacementx=0, displacementy=0; - auto &box = allOutputs.box; - if (!mFlip) { - if (mOrientation==wxHORIZONTAL) { - int d = mTop + box.GetHeight() + 5; - box.Offset(0,d); - box.Inflate(0,5); - displacementx=0; - displacementy=d; - } - else { - int d = mLeft - box.GetLeft() + 5; - box.Offset(d,0); - box.Inflate(5,0); - displacementx=d; - displacementy=0; - } - } - else { - if (mOrientation==wxHORIZONTAL) { - box.Inflate(0,5); - displacementx=0; - displacementy=0; - } - } - auto update = [=]( Label &label ){ - label.lx += displacementx; - label.ly += displacementy; - }; - for( auto &label : allOutputs.majorLabels ) - update( label ); - for( auto &label : allOutputs.minorLabels ) - update( label ); - for( auto &label : allOutputs.minorMinorLabels ) - update( label ); -} - -void Ruler::ChooseFonts( wxDC &dc ) const -{ - Updater::ChooseFonts( mpFonts, mpUserFonts.get(), dc, - mOrientation == wxHORIZONTAL - ? mBottom - mTop - 5 // height less ticks and 1px gap - : MaxPixelHeight - ); + } void Ruler::UpdateCache( @@ -1368,10 +418,6 @@ void Ruler::UpdateCache( if ( mpCache ) return; - const ZoomInfo *zoomInfo = NULL; - if (!mLog && mOrientation == wxHORIZONTAL) - zoomInfo = mUseZoomInfo; - // This gets called when something has been changed // (i.e. we've been invalidated). Recompute all // tick positions and font size. @@ -1384,45 +430,37 @@ void Ruler::UpdateCache( // Values of mLength of zero or below cause bad array allocations and // division by zero. So... // IF too small THEN bail out and don't draw. - if( mLength <= 0 ) + if( mRulerStruct.mLength <= 0 ) return; - if (mOrientation == wxHORIZONTAL) - cache.mRect = { 0, 0, mLength, 0 }; + if (mRulerStruct.mOrientation == wxHORIZONTAL) + cache.mRect = { 0, 0, mRulerStruct.mLength, 0 }; else - cache.mRect = { 0, 0, 0, mLength }; - - // FIXME: Surely we do not need to allocate storage for the labels? - // We can just recompute them as we need them? Yes, but only if - // mCustom is false!!!! - - if(!mCustom) { - cache.mMajorLabels.clear(); - cache.mMinorLabels.clear(); - cache.mMinorMinorLabels.clear(); - } + cache.mRect = { 0, 0, 0, mRulerStruct.mLength }; cache.mBits = mUserBits; - cache.mBits.resize( static_cast(mLength + 1), false ); + cache.mBits.resize( static_cast(mRulerStruct.mLength + 1), false ); - // Keep Updater const! We want no hidden state changes affecting its - // computations. - const Updater updater{ *this, zoomInfo }; + cache.mMajorLabels = mCustomMajorLabels; + cache.mMinorLabels = mCustomMinorLabels; + cache.mMinorMinorLabels = mCustomMinorMinorLabels; + Updater::UpdateOutputs allOutputs{ cache.mMajorLabels, cache.mMinorLabels, cache.mMinorMinorLabels, cache.mBits, cache.mRect }; - updater.Update(dc, envelope, allOutputs); + if (mpUpdater != nullptr) + mpUpdater->Update(dc, envelope, allOutputs, mRulerStruct); } -auto Ruler::GetFonts() const -> Fonts +auto Ruler::GetFonts() const -> RulerStruct::Fonts { - if ( !mpFonts ) { + if ( !mRulerStruct.mpFonts ) { wxScreenDC dc; ChooseFonts( dc ); } - return *mpFonts; + return *(mRulerStruct.mpFonts); } void Ruler::Draw(wxDC& dc) const @@ -1432,7 +470,7 @@ void Ruler::Draw(wxDC& dc) const void Ruler::Draw(wxDC& dc, const Envelope* envelope) const { - if( mLength <=0 ) + if(mRulerStruct.mLength <=0 ) return; UpdateCache( dc, envelope ); @@ -1448,69 +486,69 @@ void Ruler::Draw(wxDC& dc, const Envelope* envelope) const // Draws a long line the length of the ruler. if( !mbTicksOnly ) { - if (mOrientation == wxHORIZONTAL) { - if (mFlip) - AColor::Line(dc, mLeft, mTop, mRight, mTop); + if (mRulerStruct.mOrientation == wxHORIZONTAL) { + if (mRulerStruct.mFlip) + AColor::Line(dc, mRulerStruct.mLeft, mRulerStruct.mTop, mRulerStruct.mRight, mRulerStruct.mTop); else - AColor::Line(dc, mLeft, mBottom, mRight, mBottom); + AColor::Line(dc, mRulerStruct.mLeft, mRulerStruct.mBottom, mRulerStruct.mRight, mRulerStruct.mBottom); } else { - if (mFlip) - AColor::Line(dc, mLeft, mTop, mLeft, mBottom); + if (mRulerStruct.mFlip) + AColor::Line(dc, mRulerStruct.mLeft, mRulerStruct.mTop, mRulerStruct.mLeft, mRulerStruct.mBottom); else { // These calculations appear to be wrong, and to never have been used (so not tested) prior to MixerBoard. // AColor::Line(dc, mRect.x-mRect.width, mTop, mRect.x-mRect.width, mBottom); - const int nLineX = mRight - 1; - AColor::Line(dc, nLineX, mTop, nLineX, mBottom); + const int nLineX = mRulerStruct.mRight - 1; + AColor::Line(dc, nLineX, mRulerStruct.mTop, nLineX, mRulerStruct.mBottom); } } } - dc.SetFont( mpFonts->major ); + dc.SetFont( mRulerStruct.mpFonts->major ); // We may want to not show the ticks at the extremes, // though still showing the labels. // This gives a better look when the ruler is on a bevelled // button, since otherwise the tick is drawn on the bevel. - int iMaxPos = (mOrientation==wxHORIZONTAL)? mRight : mBottom-5; + int iMaxPos = (mRulerStruct.mOrientation==wxHORIZONTAL)? mRulerStruct.mRight : mRulerStruct.mBottom-5; - auto drawLabel = [this, iMaxPos, &dc]( const Label &label, int length ){ + auto drawLabel = [this, iMaxPos, &dc]( const Updater::Label &label, int length ){ int pos = label.pos; if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos))) { - if (mOrientation == wxHORIZONTAL) { - if (mFlip) - AColor::Line(dc, mLeft + pos, mTop, - mLeft + pos, mTop + length); + if (mRulerStruct.mOrientation == wxHORIZONTAL) { + if (mRulerStruct.mFlip) + AColor::Line(dc, mRulerStruct.mLeft + pos, mRulerStruct.mTop, + mRulerStruct.mLeft + pos, mRulerStruct.mTop + length); else - AColor::Line(dc, mLeft + pos, mBottom - length, - mLeft + pos, mBottom); + AColor::Line(dc, mRulerStruct.mLeft + pos, mRulerStruct.mBottom - length, + mRulerStruct.mLeft + pos, mRulerStruct.mBottom); } else { - if (mFlip) - AColor::Line(dc, mLeft, mTop + pos, - mLeft + length, mTop + pos); + if (mRulerStruct.mFlip) + AColor::Line(dc, mRulerStruct.mLeft, mRulerStruct.mTop + pos, + mRulerStruct.mLeft + length, mRulerStruct.mTop + pos); else - AColor::Line(dc, mRight - length, mTop + pos, - mRight, mTop + pos); + AColor::Line(dc, mRulerStruct.mRight - length, mRulerStruct.mTop + pos, + mRulerStruct.mRight, mRulerStruct.mTop + pos); } } - label.Draw(dc, mTwoTone, mTickColour); + label.Draw(dc, mTwoTone, mTickColour, mRulerStruct.mpFonts); }; for( const auto &label : cache.mMajorLabels ) drawLabel( label, 4 ); if( mbMinor ) { - dc.SetFont( mpFonts->minor ); + dc.SetFont( mRulerStruct.mpFonts->minor ); for( const auto &label : cache.mMinorLabels ) drawLabel( label, 2 ); } - dc.SetFont( mpFonts->minorMinor ); + dc.SetFont( mRulerStruct.mpFonts->minorMinor ); for( const auto &label : cache.mMinorMinorLabels ) if ( !label.text.empty() ) @@ -1534,7 +572,7 @@ void Ruler::DrawGrid(wxDC& dc, dc.SetPen(gridPen); for( const auto &label : cache.mMinorLabels ) { gridPos = label.pos; - if(mOrientation == wxHORIZONTAL) { + if(mRulerStruct.mOrientation == wxHORIZONTAL) { if((gridPos != 0) && (gridPos != gridLineLength)) AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset); } @@ -1550,7 +588,7 @@ void Ruler::DrawGrid(wxDC& dc, dc.SetPen(gridPen); for( const auto &label : cache.mMajorLabels ) { gridPos = label.pos; - if(mOrientation == wxHORIZONTAL) { + if(mRulerStruct.mOrientation == wxHORIZONTAL) { if((gridPos != 0) && (gridPos != gridLineLength)) AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset); } @@ -1564,7 +602,7 @@ void Ruler::DrawGrid(wxDC& dc, if(zeroPosition > 0) { // Draw 'zero' grid line in black dc.SetPen(*wxBLACK_PEN); - if(mOrientation == wxHORIZONTAL) { + if(mRulerStruct.mOrientation == wxHORIZONTAL) { if(zeroPosition != gridLineLength) AColor::Line(dc, zeroPosition+xOffset, yOffset, zeroPosition+xOffset, gridLineLength-1+yOffset); } @@ -1576,10 +614,10 @@ void Ruler::DrawGrid(wxDC& dc, } } -int Ruler::FindZero( const Labels &labels ) const +int Ruler::FindZero( const Updater::Labels &labels ) const { auto begin = labels.begin(), end = labels.end(), - iter = std::find_if( begin, end, []( const Label &label ){ + iter = std::find_if( begin, end, []( const Updater::Label &label ){ return label.value == 0.0; } ); @@ -1615,164 +653,49 @@ void Ruler::GetMaxSize(wxCoord *width, wxCoord *height) *height = cache.mRect.GetHeight(); } - -void Ruler::SetCustomMode(bool value) +void Ruler::ResetCustomLabels( + bool resetMajor, bool resetMinor, bool resetMinorMinor) { - if ( mCustom != value ) { - mCustom = value; - Invalidate(); - } + if (resetMajor) + mCustomMajorLabels.clear(); + if (resetMinor) + mCustomMinorLabels.clear(); + if (resetMinorMinor) + mCustomMinorMinorLabels.clear(); } -#if 0 -// These two unused functions need reconsideration of their interactions with -// the cache and update void Ruler::SetCustomMajorLabels( - const TranslatableStrings &labels, int start, int step) + const Updater::Labels& labels) { - SetCustomMode( true ); - mpCache = std::make_unique(); - auto &cache = *mpCache; - auto &mMajorLabels = cache.mMajorLabels; - const auto numLabel = labels.size(); - mMajorLabels.resize( numLabel ); for(size_t i = 0; i(); - auto &cache = *mpCache; - auto &mMinorLabels = cache.mMinorLabels; - const auto numLabel = labels.size(); - mMinorLabels.resize( numLabel ); - for(size_t i = 0; i // member variable @@ -26,14 +27,6 @@ class ZoomInfo; class AUDACITY_DLL_API Ruler { public: - enum RulerFormat { - IntFormat, - RealFormat, - RealLogFormat, - TimeFormat, - LinearDBFormat, - }; - // // Constructor / Destructor // @@ -62,6 +55,12 @@ class AUDACITY_DLL_API Ruler { // (at the center of the pixel, in both cases) void SetRange(double min, double max, double hiddenMin, double hiddenMax); + // Set the kind of updater the ruler will use (Linear, Logarithmic, Custom, etc.) + void SetUpdater(std::unique_ptr pUpdater); + + // An overload to also set ZoomInfo while adjusting updater + void SetUpdater(std::unique_ptr pUpdater, int leftOffset); + // // Optional Ruler Parameters // @@ -77,9 +76,6 @@ class AUDACITY_DLL_API Ruler { void SetUnits(const TranslatableString &units); void SetDbMirrorValue( const double d ); - // Logarithmic - void SetLog(bool log); - // Minimum number of pixels between labels void SetSpacing(int spacing); @@ -97,11 +93,7 @@ class AUDACITY_DLL_API Ruler { // Good defaults are provided, but you can override here void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont); - struct Fonts { - wxFont major, minor, minorMinor; - int lead; - }; - Fonts GetFonts() const; + RulerStruct::Fonts GetFonts() const; void SetNumberScale(const NumberScale &scale); @@ -119,18 +111,18 @@ class AUDACITY_DLL_API Ruler { // The following functions should allow a custom ruler setup: // autosize is a GREAT thing, but for some applications it's - // useful the definition of a label array and label step by - // the user. - void SetCustomMode(bool value); + // useful the definition of a label array and label step by the user. // If this is the case, you should provide an array of labels, start // label position, and labels step. The range eventually specified will be // ignored. + void ResetCustomLabels( + bool resetMajor, bool resetMinor, bool resetMinorMinor); void SetCustomMajorLabels( - const TranslatableStrings &labels, int start, int step); + const Updater::Labels &labels); void SetCustomMinorLabels( - const TranslatableStrings &labels, int start, int step); - - void SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo); + const Updater::Labels& labels); + void SetCustomMinorMinorLabels( + const Updater::Labels& labels); // // Drawing @@ -152,143 +144,43 @@ class AUDACITY_DLL_API Ruler { void Invalidate(); private: - struct TickSizes; - - class Label { - public: - double value; - int pos; - int lx, ly; - TranslatableString text; - - void Draw(wxDC &dc, bool twoTone, wxColour c) const; - }; - using Labels = std::vector