diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp index 5d16b098c35f..aadcdbe1542d 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,7 +1282,7 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* project, mOuter = GetClientRect(); - mRuler.SetUseZoomInfo(mLeftOffset, mViewInfo); + mRuler.SetUpdater( std::make_unique( mRuler, mViewInfo ), mLeftOffset ); mRuler.SetLabelEdges( false ); mRuler.SetFormat( Ruler::TimeFormat ); @@ -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( mRuler, 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..a2b8d84d18d7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1013,6 +1013,8 @@ list( APPEND SOURCES widgets/BackedPanel.h widgets/BasicMenu.cpp widgets/BasicMenu.h + widgets/CustomUpdater.cpp + widgets/CustomUpdater.h widgets/ErrorDialog.cpp widgets/ErrorDialog.h widgets/ExpandingToolBar.cpp @@ -1045,6 +1047,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 @@ -1070,6 +1076,8 @@ list( APPEND SOURCES > 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..071ec4397922 100644 --- a/src/FreqWindow.cpp +++ b/src/FreqWindow.cpp @@ -83,6 +83,8 @@ the mouse around. #include "./widgets/HelpSystem.h" #include "widgets/AudacityMessageBox.h" #include "widgets/Ruler.h" +#include "widgets/LinearUpdater.h" +#include "widgets/LogarithmicUpdater.h" #include "widgets/VetoDialogHook.h" #if wxUSE_ACCESSIBILITY @@ -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, nullptr)); vRuler->ruler.SetRange(0.0, -dBRange); - hRuler->ruler.SetLog(false); + hRuler->ruler.SetUpdater(std::make_unique(hRuler->ruler, nullptr)); hRuler->ruler.SetRange(0, 1); DrawBackground(memDC); @@ -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(hRuler->ruler, nullptr)); } else { xStep = (xMax - xMin) / width; - hRuler->ruler.SetLog(false); + hRuler->ruler.SetUpdater(std::make_unique(hRuler->ruler, nullptr)); } 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(hRuler->ruler, nullptr)); /* i18n-hint: short form of 'seconds'.*/ hRuler->ruler.SetUnits(XO("s")); } diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index bc80c566b2c0..6cd433ac57f8 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,7 +75,7 @@ void TimeTrack::CleanState() SetName(GetDefaultName()); mRuler = std::make_unique(); - mRuler->SetUseZoomInfo(0, mZoomInfo); + mRuler->SetUpdater(std::make_unique(*(mRuler.get()), mZoomInfo), 0); mRuler->SetLabelEdges(false); mRuler->SetFormat(Ruler::TimeFormat); } @@ -102,7 +103,7 @@ 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(*(mRuler.get()), mZoomInfo), 0); mRuler->SetLabelEdges(false); mRuler->SetFormat(Ruler::TimeFormat); } diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp index 946813a8ef84..e49463a475d2 100644 --- a/src/effects/Equalization.cpp +++ b/src/effects/Equalization.cpp @@ -103,6 +103,8 @@ #include "../WaveClip.h" #include "ViewInfo.h" #include "../WaveTrack.h" +#include "../widgets/LinearUpdater.h" +#include "../widgets/LogarithmicUpdater.h" #include "../widgets/Ruler.h" #include "../widgets/AudacityTextEntryDialog.h" #include "XMLFileReader.h" @@ -2288,7 +2290,7 @@ void EffectEqualization::UpdateDraw() { EnvLogToLin(); mEnvelope = mLinEnvelope.get(); - mFreqRuler->ruler.SetLog(false); + mFreqRuler->ruler.SetUpdater(std::make_unique(mFreqRuler->ruler, nullptr)); 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, nullptr)); 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, nullptr)); 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, nullptr)); mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq); EnvLinToLog(); mEnvelope = mLogEnvelope.get(); diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp index 9d203217a0f4..fe533718fe52 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; @@ -167,7 +169,7 @@ void SpectrumVRulerControls::DoUpdateVRuler( vruler->SetRange((int)(maxFreq), (int)(minFreq)); vruler->SetUnits({}); } - vruler->SetLog(false); + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); } break; case SpectrogramSettings::stLogarithmic: @@ -189,7 +191,7 @@ void SpectrumVRulerControls::DoUpdateVRuler( vruler->SetLabelEdges(true); vruler->SetRange(maxFreq, minFreq); vruler->SetUnits({}); - vruler->SetLog(true); + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); 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..af2f7acd585f 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp @@ -21,6 +21,7 @@ Paul Licameli split from WaveTrackVRulerControls.cpp #include "../../../../WaveTrack.h" #include "../../../../prefs/WaveformSettings.h" #include "../../../../widgets/Ruler.h" +#include "../../../../widgets/LinearUpdater.h" WaveformVRulerControls::~WaveformVRulerControls() = default; @@ -222,7 +223,7 @@ void WaveformVRulerControls::DoUpdateVRuler( vruler->SetFormat(Ruler::RealFormat); vruler->SetUnits({}); vruler->SetLabelEdges(false); - vruler->SetLog(false); + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); } else { wxASSERT(scaleType == WaveformSettings::stLogarithmic); @@ -330,7 +331,7 @@ void WaveformVRulerControls::DoUpdateVRuler( #endif vruler->SetFormat(Ruler::RealLogFormat); vruler->SetLabelEdges(true); - vruler->SetLog(false); + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); } 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..541500d1e2fd 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() { @@ -120,7 +122,10 @@ void TimeTrackVRulerControls::UpdateRuler( const wxRect &rect ) vruler->SetFormat((tt->GetDisplayLog()) ? Ruler::RealLogFormat : Ruler::RealFormat); vruler->SetUnits({}); vruler->SetLabelEdges(false); - vruler->SetLog(tt->GetDisplayLog()); + if (tt->GetDisplayLog()) + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); + else + vruler->SetUpdater(std::make_unique(*vruler, nullptr)); 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..f4b74520f264 --- /dev/null +++ b/src/widgets/CustomUpdater.cpp @@ -0,0 +1,32 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + + +#include "CustomUpdater.h" + +void CustomUpdater::Update( + wxDC& dc, const Envelope* envelope, UpdateOutputs& allOutputs) const +{ + const int mLength = mRuler.mLength; + const Ruler::Fonts& mFonts = *mRuler.mpFonts; + + 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 < numLabel) && (i <= mLength); ++i) + TickCustom(dc, i, mFonts.major, majorOutputs); + + BoxAdjust(allOutputs); +} diff --git a/src/widgets/CustomUpdater.h b/src/widgets/CustomUpdater.h new file mode 100644 index 000000000000..760f6f728e42 --- /dev/null +++ b/src/widgets/CustomUpdater.h @@ -0,0 +1,27 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CustomUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_CUSTOM_UPDATER__ +#define __AUDACITY_CUSTOM_UPDATER__ + +#include "Updater.h" + +struct CustomUpdater : public Updater { + explicit CustomUpdater(const Ruler& ruler, const ZoomInfo* z) + : Updater{ ruler, NULL } + {} + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs + ) const override; +}; + +#endif diff --git a/src/widgets/LinearUpdater.cpp b/src/widgets/LinearUpdater.cpp new file mode 100644 index 000000000000..9251c08c0fb2 --- /dev/null +++ b/src/widgets/LinearUpdater.cpp @@ -0,0 +1,180 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LinearUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + +#include "LinearUpdater.h" + +void LinearUpdater::Update( + wxDC& dc, const Envelope* envelope, UpdateOutputs& allOutputs) const +{ + TickOutputs majorOutputs{ + allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; + + const double mDbMirrorValue = mRuler.mDbMirrorValue; + const int mLength = mRuler.mLength; + const Ruler::RulerFormat mFormat = mRuler.mFormat; + + const int mLeft = mRuler.mLeft; + const int mTop = mRuler.mTop; + const int mBottom = mRuler.mBottom; + const int mRight = mRuler.mRight; + const int mOrientation = mRuler.mOrientation; + + const double mMin = mRuler.mMin; + const double mMax = mRuler.mMax; + const double mHiddenMin = mRuler.mHiddenMin; + const double mHiddenMax = mRuler.mHiddenMax; + + const Ruler::Fonts& mFonts = *mRuler.mpFonts; + const bool mLabelEdges = mRuler.mLabelEdges; + const int mLeftOffset = mRuler.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] + (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); + 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 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); + 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); + } + + BoxAdjust(allOutputs); +} diff --git a/src/widgets/LinearUpdater.h b/src/widgets/LinearUpdater.h new file mode 100644 index 000000000000..e35a49a6e7e5 --- /dev/null +++ b/src/widgets/LinearUpdater.h @@ -0,0 +1,27 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LinearUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_LINEAR_UPDATER__ +#define __AUDACITY_LINEAR_UPDATER__ + +#include "Updater.h" + +struct LinearUpdater : public Updater { + explicit LinearUpdater(const Ruler& ruler, const ZoomInfo* z) + : Updater{ ruler, z } + {} + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs + ) const override; +}; + +#endif diff --git a/src/widgets/LogarithmicUpdater.cpp b/src/widgets/LogarithmicUpdater.cpp new file mode 100644 index 000000000000..8a3f43424b6c --- /dev/null +++ b/src/widgets/LogarithmicUpdater.cpp @@ -0,0 +1,121 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LogarithmicUpdater.cpp + + Dominic Mazzoni + +**********************************************************************/ + +#include "LogarithmicUpdater.h" + +void LogarithmicUpdater::Update( + wxDC& dc, const Envelope* envelope, UpdateOutputs& allOutputs) const +{ + TickOutputs majorOutputs{ + allOutputs.majorLabels, allOutputs.bits, allOutputs.box }; + + const int mLength = mRuler.mLength; + const Ruler::RulerFormat mFormat = mRuler.mFormat; + + const int mOrientation = mRuler.mOrientation; + + const double mMin = mRuler.mMin; + const double mMax = mRuler.mMax; + const double mHiddenMin = mRuler.mHiddenMin; + const double mHiddenMax = mRuler.mHiddenMax; + + const Ruler::Fonts& mFonts = *mRuler.mpFonts; + const NumberScale mNumberScale = mRuler.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); + } + } + 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 == Ruler::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; + } + + BoxAdjust(allOutputs); +} diff --git a/src/widgets/LogarithmicUpdater.h b/src/widgets/LogarithmicUpdater.h new file mode 100644 index 000000000000..bf02d0e92674 --- /dev/null +++ b/src/widgets/LogarithmicUpdater.h @@ -0,0 +1,28 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + LogarithmicUpdater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_LOGARITHMIC_UPDATER__ +#define __AUDACITY_LOGARITHMIC_UPDATER__ + +#include "Updater.h" + +struct LogarithmicUpdater : public Updater { + explicit LogarithmicUpdater(const Ruler& ruler, const ZoomInfo* z) + : Updater{ ruler, NULL } + {} + + void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs + ) const override; +}; + +#endif + diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index df878074cbe0..9aa6314ca3ab 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -67,6 +67,12 @@ array of Ruler::Label. #include "Theme.h" #include "ViewInfo.h" +#include "Updater.h" +// Need to include to set default +#include "LinearUpdater.h" +// Needed for RulerPanel +#include "LogarithmicUpdater.h" + using std::min; using std::max; @@ -116,6 +122,12 @@ Ruler::Ruler() 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( *this, mUseZoomInfo ); + // mpUpdater = nullptr; } Ruler::~Ruler() @@ -139,15 +151,29 @@ void Ruler::SetFormat(RulerFormat format) } } -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); + 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 (mLeftOffset != leftOffset) + mLeftOffset = leftOffset; + + // Hm, is this invalidation sufficient? What if *zoomInfo changes under us? + if (mUseZoomInfo != mpUpdater->zoomInfo) + mUseZoomInfo = mpUpdater->zoomInfo; + + Invalidate(); } void Ruler::SetUnits(const TranslatableString &units) @@ -354,407 +380,6 @@ void Ruler::Invalidate() // 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, @@ -846,157 +471,12 @@ auto Ruler::MakeTick( return { { strLeft, strTop, strW, strH }, lab }; } -struct Ruler::Updater { - const Ruler &mRuler; - const ZoomInfo *zoomInfo; - - explicit Updater( const Ruler &ruler, const ZoomInfo *z ) - : mRuler{ ruler } - , zoomInfo{ z } - {} - - const double mDbMirrorValue = mRuler.mDbMirrorValue; - const int mLength = mRuler.mLength; - const RulerFormat mFormat = mRuler.mFormat; - const TranslatableString mUnits = mRuler.mUnits; - - const int mLeft = mRuler.mLeft; - const int mTop = mRuler.mTop; - const int mBottom = mRuler.mBottom; - const int mRight = mRuler.mRight; - - const int mSpacing = mRuler.mSpacing; - const int mOrientation = mRuler.mOrientation; - const bool mFlip = mRuler.mFlip; - - const bool mCustom = mRuler.mCustom; - const Fonts &mFonts = *mRuler.mpFonts; - const bool mLog = mRuler.mLog; - const double mHiddenMin = mRuler.mHiddenMin; - const double mHiddenMax = mRuler.mHiddenMax; - const bool mLabelEdges = mRuler.mLabelEdges; - const double mMin = mRuler.mMin; - const double mMax = mRuler.mMax; - const int mLeftOffset = mRuler.mLeftOffset; - const NumberScale mNumberScale = mRuler.mNumberScale; - - struct TickOutputs; - - bool Tick( wxDC &dc, - int pos, double d, const TickSizes &tickSizes, wxFont font, - TickOutputs outputs - ) const; - - // Another tick generator for custom ruler case (noauto) . - bool TickCustom( wxDC &dc, int labelIdx, wxFont font, - TickOutputs outputs - ) const; - - static void ChooseFonts( - std::unique_ptr &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; 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 +494,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 Fonts* pUserFonts = mpUserFonts.get(); + int desiredPixelHeight = mOrientation == wxHORIZONTAL + ? mBottom - mTop - 5 // height less ticks and 1px gap + : MaxPixelHeight; + + if (mpFonts) return; - if ( pUserFonts ) { - pFonts = std::make_unique( *pUserFonts ); + if (pUserFonts) { + mpFonts = std::make_unique(*pUserFonts); return; } - pFonts = std::make_unique( Fonts{ {}, {}, {}, 0 } ); - auto &fonts = *pFonts; + mpFonts = std::make_unique(Fonts{ {}, {}, {}, 0 }); + auto& fonts = *mpFonts; int fontSize = 4; @@ -1036,329 +519,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 +540,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. @@ -1404,15 +572,12 @@ void Ruler::UpdateCache( cache.mBits = mUserBits; cache.mBits.resize( static_cast(mLength + 1), false ); - - // Keep Updater const! We want no hidden state changes affecting its - // computations. - const Updater updater{ *this, zoomInfo }; + Updater::UpdateOutputs allOutputs{ cache.mMajorLabels, cache.mMinorLabels, cache.mMinorMinorLabels, cache.mBits, cache.mRect }; - updater.Update(dc, envelope, allOutputs); + mpUpdater->Update(dc, envelope, allOutputs); } auto Ruler::GetFonts() const -> Fonts @@ -1677,19 +842,6 @@ void Ruler::Label::Draw(wxDC&dc, bool twoTone, wxColour c) const } } -void Ruler::SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo) -{ - - if ( mLeftOffset != leftOffset || - // Hm, is this invalidation sufficient? What if *zoomInfo changes under us? - mUseZoomInfo != zoomInfo - ) { - mLeftOffset = leftOffset; - mUseZoomInfo = zoomInfo; - Invalidate(); - } -} - // // RulerPanel // @@ -1716,7 +868,10 @@ RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id, ruler.SetBounds( 0, 0, bounds.x, bounds.y ); ruler.SetOrientation(orientation); ruler.SetRange( range.first, range.second ); - ruler.SetLog( options.log ); + if (options.log) + ruler.SetUpdater(std::make_unique(ruler, nullptr)); + else + ruler.SetUpdater(std::make_unique(ruler, nullptr)); ruler.SetFormat(format); ruler.SetUnits( units ); ruler.SetFlip( options.flip ); diff --git a/src/widgets/Ruler.h b/src/widgets/Ruler.h index 41b0155e0852..dcede69b6336 100644 --- a/src/widgets/Ruler.h +++ b/src/widgets/Ruler.h @@ -22,6 +22,7 @@ class wxFont; class Envelope; class ZoomInfo; +struct Updater; class AUDACITY_DLL_API Ruler { public: @@ -62,6 +63,13 @@ 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 replace SetUseZoomInfo + void SetUpdater(std::unique_ptr pUpdater, int leftOffset); + // // Optional Ruler Parameters // @@ -77,9 +85,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); @@ -130,8 +135,6 @@ class AUDACITY_DLL_API Ruler { void SetCustomMinorLabels( const TranslatableStrings &labels, int start, int step); - void SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo); - // // Drawing // @@ -152,7 +155,10 @@ class AUDACITY_DLL_API Ruler { void Invalidate(); private: - struct TickSizes; + friend class Updater; + friend class LinearUpdater; + friend class LogarithmicUpdater; + friend class CustomUpdater; class Label { public: @@ -170,8 +176,6 @@ class AUDACITY_DLL_API Ruler { void ChooseFonts( wxDC &dc ) const; void UpdateCache( wxDC &dc, const Envelope* envelope ) const; - - struct Updater; public: bool mbTicksOnly; // true => no line the length of the ruler @@ -187,6 +191,8 @@ class AUDACITY_DLL_API Ruler { std::unique_ptr mpUserFonts; mutable std::unique_ptr mpFonts; + std::unique_ptr mpUpdater; + double mMin, mMax; double mHiddenMin, mHiddenMax; diff --git a/src/widgets/Updater.cpp b/src/widgets/Updater.cpp new file mode 100644 index 000000000000..df5d04a06df9 --- /dev/null +++ b/src/widgets/Updater.cpp @@ -0,0 +1,552 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Updater.cpp + + Dominic Mazzoni + +*******************************************************************//** + +\class Updater +\brief Used to update a Ruler. + + This is a pure virtual class which sets how a ruler will generate + its values. + + It supports three subclasses: LinearUpdater, LogarithmicUpdater, + and CustomUpdater + +*//******************************************************************/ + +#include "Updater.h" + +Updater::TickSizes::TickSizes(double UPP, int orientation, Ruler::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 Ruler::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 Ruler::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 Ruler::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 Ruler::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 Ruler::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 Updater::TickSizes::LabelString( + double d, Ruler::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 != Ruler::RealLogFormat)) + d = 0.0; + + switch (format) { + case Ruler::IntFormat: + s.Printf(wxT("%d"), (int)floor(d + 0.5)); + break; + case Ruler::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 Ruler::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 Ruler::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 Ruler::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; + } + +void Updater::BoxAdjust( + UpdateOutputs& allOutputs +) +const +{ + const int mLeft = mRuler.mLeft; + const int mTop = mRuler.mTop; + const int mBottom = mRuler.mBottom; + const int mRight = mRuler.mRight; + const int mOrientation = mRuler.mOrientation; + const bool mFlip = mRuler.mFlip; + + 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 = [=](Ruler::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); +} + +bool Updater::Tick(wxDC& dc, + int pos, double d, const TickSizes& tickSizes, wxFont font, + // in/out: + TickOutputs outputs) const +{ + const double mDbMirrorValue = mRuler.mDbMirrorValue; + const int mLength = mRuler.mLength; + const Ruler::RulerFormat mFormat = mRuler.mFormat; + + const int mLeft = mRuler.mLeft; + const int mTop = mRuler.mTop; + const int mBottom = mRuler.mBottom; + const int mRight = mRuler.mRight; + const int mOrientation = mRuler.mOrientation; + + const Ruler::Fonts& mFonts = *mRuler.mpFonts; + const TranslatableString mUnits = mRuler.mUnits; + const int mSpacing = mRuler.mSpacing; + const bool mFlip = mRuler.mFlip; + + // 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; + + Ruler::Label lab; + lab.value = d; + lab.pos = pos; + lab.text = tickSizes.LabelString(d, mFormat, mUnits); + + const auto result = Ruler::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 Updater::TickCustom(wxDC& dc, int labelIdx, wxFont font, + // in/out: + TickOutputs outputs) const +{ + const int mLeft = mRuler.mLeft; + const int mTop = mRuler.mTop; + const int mOrientation = mRuler.mOrientation; + + const Ruler::Fonts& mFonts = *mRuler.mpFonts; + const int mSpacing = mRuler.mSpacing; + const bool mFlip = mRuler.mFlip; + + // 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 + + Ruler::Label lab; + lab.value = 0.0; + + const auto result = Ruler::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(); +} + +// Theoretically this helps +void Updater::Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs +) const {} diff --git a/src/widgets/Updater.h b/src/widgets/Updater.h new file mode 100644 index 000000000000..9bbc8e401fa5 --- /dev/null +++ b/src/widgets/Updater.h @@ -0,0 +1,83 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Updater.h + + Dominic Mazzoni + +**********************************************************************/ + +#ifndef __AUDACITY_UPDATER__ +#define __AUDACITY_UPDATER__ + +#include "Ruler.h" +#include "ViewInfo.h" // for children + +struct Updater { + const Ruler& mRuler; + const ZoomInfo* zoomInfo; + + explicit Updater(const Ruler& ruler, const ZoomInfo* z) + : mRuler{ ruler } + , zoomInfo{ z } + {} + ~Updater() {} + + struct TickOutputs { Ruler::Labels& labels; Ruler::Bits& bits; wxRect& box; }; + struct UpdateOutputs { + Ruler::Labels& majorLabels, & minorLabels, & minorMinorLabels; + Ruler::Bits& bits; + wxRect& box; + }; + + + struct TickSizes + { + bool useMajor = true; + + double mMajor; + double mMinor; + + int mDigits; + + TickSizes(double UPP, int orientation, Ruler::RulerFormat format, bool log); + + TranslatableString LabelString( + double d, Ruler::RulerFormat format, const TranslatableString& units) + const; + }; + + double ComputeWarpedLength(const Envelope& env, double t0, double t1) const + { + return env.IntegralOfInverse(t0, t1); + } + + double SolveWarpedLength(const Envelope& env, double t0, double length) const + { + return env.SolveIntegralOfInverse(t0, length); + } + + bool Tick(wxDC& dc, + int pos, double d, const TickSizes& tickSizes, wxFont font, + TickOutputs outputs + ) const; + + // Another tick generator for custom ruler case (noauto) . + bool TickCustom(wxDC& dc, int labelIdx, wxFont font, + TickOutputs outputs + ) const; + + void BoxAdjust( + UpdateOutputs& allOutputs + ) + const; + + virtual void Update( + wxDC& dc, const Envelope* envelope, + UpdateOutputs& allOutputs + )// Envelope *speedEnv, long minSpeed, long maxSpeed ) + const = 0; +}; + +#endif //define __AUDACITY_UPDATER__