/**********************************************************************

  Audacity: A Digital Audio Editor

  Ruler.cpp

  Dominic Mazzoni

*******************************************************************//**

\class Ruler
\brief Used to display a Ruler.

  This is a generic class which can be used to display just about
  any kind of ruler.

  At a minimum, the user must specify the dimensions of the
  ruler, its orientation (horizontal or vertical), and the
  values displayed at the two ends of the ruler (min and max).
  By default, this class will display tick marks at reasonable
  round numbers and fractions, for example, 100, 50, 10, 5, 1,
  0.5, 0.1, etc.

  The class is designed to display a small handful of
  labeled Major ticks, and a few Minor ticks between each of
  these.  Minor ticks are labeled if there is enough space.
  Labels will never run into each other.

  In addition to Real numbers, the Ruler currently supports
  two other formats for its display:

  Integer - never shows tick marks for fractions of an integer

  Time - Assumes values represent seconds, and labels the tick
         marks in "HH:MM:SS" format, e.g. 4000 seconds becomes
         "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.

*//******************************************************************/


#include "Ruler.h"

#include <wx/dcclient.h>
#include <wx/dcscreen.h>

// Tenacity libraries
#include <lib-screen-geometry/NumberScale.h>
#include <lib-screen-geometry/ViewInfo.h>

#include "../AColor.h"
#include "../theme/AllThemeResources.h"
#include "../Envelope.h"
#include "../theme/Theme.h"

using std::min;
using std::max;

//wxColour Ruler::mTickColour{ 153, 153, 153 };

//
// Ruler
//

Ruler::Ruler()
{
   mMin = 0.0;
   mMax = 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
   // calls SetFonts manually.  So the defaults here are not used
   // often.

   int fontSize = 10;
#ifdef __WXMSW__
   fontSize = 8;
#endif

   mLength = 0;

   mCustom = false;
   mbMinor = true;

   mTwoTone = false;

   mUseZoomInfo = NULL;
}

Ruler::~Ruler()
{
   Invalidate();  // frees up our arrays
}

void Ruler::SetTwoTone(bool twoTone)
{
   mTwoTone = twoTone;
}

void Ruler::SetFormat(RulerFormat format)
{
   // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat

   if (mFormat != format) {
      mFormat = format;

      Invalidate();
   }
}

void Ruler::SetLog(bool log)
{
   // Logarithmic

   if (mLog != log) {
      mLog = log;

      Invalidate();
   }
}

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;

      Invalidate();
   }
}

void Ruler::SetDbMirrorValue( const double d )
{
   if (mDbMirrorValue != d) {
      mDbMirrorValue = d;

      Invalidate();
   }
}

void Ruler::SetOrientation(int orient)
{
   // wxHORIZONTAL || wxVERTICAL

   if (mOrientation != orient) {
      mOrientation = orient;

      if (mOrientation == wxVERTICAL && !mHasSetSpacing)
         mSpacing = 2;

      Invalidate();
   }
}

void Ruler::SetRange(double min, double max)
{
   // For a horizontal ruler,
   // min is the value in the center of pixel "left",
   // max is the value in the center of pixel "right".
   if (mMin != min || mMax != max)
   {
      mMin = min;
      mMax = max;

      Invalidate();
   }
}

void Ruler::SetSpacing(int spacing)
{
   mHasSetSpacing = true;

   if (mSpacing != spacing) {
      mSpacing = spacing;

      Invalidate();
   }
}

void Ruler::SetLabelEdges(bool labelEdges)
{
   // If this is true, the edges of the ruler will always
   // receive a label.  If not, the nearest round number is
   // labeled (which may or may not be the edge).

   if (mLabelEdges != labelEdges) {
      mLabelEdges = labelEdges;

      Invalidate();
   }
}

void Ruler::SetFlip(bool flip)
{
   // If this is true, the orientation of the tick marks
   // is reversed from the default; eg. above the line
   // instead of below

   if (mFlip != flip) {
      mFlip = flip;

      Invalidate();
   }
}

void Ruler::SetMinor(bool value)
{
   mbMinor = value;
}

namespace {
void FindFontHeights(
   wxCoord &height, wxCoord &lead, wxDC &dc, const wxFont &font )
{
   wxCoord strW, strH, strD, strL;
   static const wxString exampleText = wxT("0.9");   //ignored for height calcs on all platforms
   dc.SetFont( font );
   dc.GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
   height = strH - strD - strL;
   lead = strL;
}

void FindFontHeights(
   wxCoord &height, wxCoord &lead,
   wxDC &dc, int fontSize, wxFontWeight weight = wxFONTWEIGHT_NORMAL )
{
   const wxFont font{ fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, weight };
   FindFontHeights( height, lead, dc, font );
}
}

void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
{
   // Won't override these fonts

   mpUserFonts = std::make_unique<Fonts>(
      Fonts{ majorFont, minorFont, minorMinorFont, 0 } );

   wxScreenDC dc;
   wxCoord height;
   FindFontHeights( height, mpUserFonts->lead, dc, majorFont );

   mpFonts.reset();
   mpFonts.reset();
   Invalidate();
}

void Ruler::SetNumberScale(const NumberScale &scale)
{
   if ( mNumberScale != scale ) {
      mNumberScale = scale;
      Invalidate();
   }
}

void Ruler::OfflimitsPixels(int start, int end)
{
   int length = mLength;
   if (mOrientation == wxHORIZONTAL)
      length = mRight - mLeft;
   else
      length = mBottom - mTop;
   if( length < 0 )
      return;

   auto size = static_cast<size_t>( length + 1 );
   if ( mUserBits.size() < size ) {
      mLength = length;
      mUserBits.resize( size, false );
   }

   if (end < start)
      std::swap( start, end );

   if (start < 0)
      start = 0;
   if (end > mLength)
      end = mLength;

   for(int i = start; i <= end; i++)
      mUserBits[i] = true;

   Invalidate();
}

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;

      Invalidate();
   }
}

void Ruler::Invalidate()
{
   if (mOrientation == wxHORIZONTAL)
      mLength = mRight-mLeft;
   else
      mLength = mBottom-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<bool> &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<strLen; i++)
      if ( bits[strPos+i] )
         return { {}, lab };

   // If not, position the label

   lab.lx = strLeft;
   lab.ly = strTop;

   // And mark these pixels, plus some surrounding
   // ones (the spacing between labels), as covered
   int leftMargin = spacing;
   if (strPos < leftMargin)
      leftMargin = strPos;
   strPos -= leftMargin;
   strLen += leftMargin;

   int rightMargin = spacing;
   if (strPos + strLen > length - spacing)
      rightMargin = length - strPos - strLen;
   strLen += rightMargin;

   for(i=0; i<strLen; i++)
      bits[strPos+i] = true;

   // Good to display the text
   lab.text = str;
   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 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<Fonts> &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;
#else
   10;
#endif

static constexpr int MaxPixelHeight =
#ifdef __WXMSW__
   14;
#elif __WXMAC__
   10;
#else
   12;
#endif


void Ruler::Updater::ChooseFonts(
   std::unique_ptr<Fonts> &pFonts, const Fonts *pUserFonts,
   wxDC &dc, int desiredPixelHeight )
{
   if ( pFonts )
      return;

   if ( pUserFonts ) {
      pFonts = std::make_unique<Fonts>( *pUserFonts );
      return;
   }

   pFonts = std::make_unique<Fonts>( Fonts{ {}, {}, {}, 0 } );
   auto &fonts = *pFonts;

   int fontSize = 4;

   desiredPixelHeight =
      std::max(MinPixelHeight, std::min(MaxPixelHeight, -desiredPixelHeight));

   // Keep making the font bigger until it's too big, then subtract one.
   wxCoord height;
   FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD );
   while (height <= desiredPixelHeight && fontSize < 40) {
      fontSize++;
      FindFontHeights( height, fonts.lead, dc, fontSize, wxFONTWEIGHT_BOLD );
   }
   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<numLabel) && (i<=mLength); ++i )
      TickCustom( dc, i, mFonts.major, majorOutputs );
}

void Ruler::Updater::UpdateLinear(
   wxDC &dc, const Envelope *envelope, UpdateOutputs &allOutputs ) const
{
   TickOutputs majorOutputs{
      allOutputs.majorLabels, allOutputs.bits, allOutputs.box };

   double UPP = (mMax-mMin)/mLength;  // Units per pixel
   TickSizes tickSizes{ UPP, mOrientation, mFormat, false };

   auto TickAtValue =
   [this, &tickSizes, &dc, &majorOutputs]
   ( 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 != 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 = (mMax-mMin)/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(
   wxDC &dc, const Envelope* envelope )
   const // Envelope *speedEnv, long minSpeed, long maxSpeed )
{
   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.

   ChooseFonts( dc );
   mpCache = std::make_unique< Cache >();
   auto &cache = *mpCache;

   // If ruler is being resized, we could end up with it being too small.
   // 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 )
      return;

   if (mOrientation == wxHORIZONTAL)
      cache.mRect = { 0, 0, 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.mBits = mUserBits;
   cache.mBits.resize( static_cast<size_t>(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);
}

auto Ruler::GetFonts() const -> Fonts
{
   if ( !mpFonts ) {
      wxScreenDC dc;
      ChooseFonts( dc );
   }

   return *mpFonts;
}

void Ruler::Draw(wxDC& dc) const
{
   Draw( dc, NULL);
}

void Ruler::Draw(wxDC& dc, const Envelope* envelope) const
{
   if( mLength <=0 )
      return;

   UpdateCache( dc, envelope );
   auto &cache = *mpCache;

   dc.SetTextForeground( mTickColour );
#ifdef EXPERIMENTAL_THEMING
   dc.SetPen(mPen);
#else
   dc.SetPen(*wxBLACK_PEN);
#endif

   // Draws a long line the length of the ruler.
   if( !mbTicksOnly )
   {
      if (mOrientation == wxHORIZONTAL) {
         if (mFlip)
            AColor::Line(dc, mLeft, mTop, mRight, mTop);
         else
            AColor::Line(dc, mLeft, mBottom, mRight, mBottom);
      }
      else {
         if (mFlip)
            AColor::Line(dc, mLeft, mTop, mLeft, 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);
         }
      }
   }

   dc.SetFont( 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;

   auto drawLabel = [this, iMaxPos, &dc]( const 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);
            else
               AColor::Line(dc, mLeft + pos, mBottom - length,
                             mLeft + pos, mBottom);
         }
         else {
            if (mFlip)
               AColor::Line(dc, mLeft, mTop + pos,
                             mLeft + length, mTop + pos);
            else
               AColor::Line(dc, mRight - length, mTop + pos,
                             mRight, mTop + pos);
         }
      }

      label.Draw(dc, mTwoTone, mTickColour);
   };

   for( const auto &label : cache.mMajorLabels )
      drawLabel( label, 4 );

   if( mbMinor ) {
      dc.SetFont( mpFonts->minor );
      for( const auto &label : cache.mMinorLabels )
         drawLabel( label, 2 );
   }

   dc.SetFont( mpFonts->minorMinor );

   for( const auto &label : cache.mMinorMinorLabels )
      if ( !label.text.empty() )
         drawLabel( label, 2 );
}

// ********** Draw grid ***************************
void Ruler::DrawGrid(wxDC& dc,
   const int gridLineLength,
   const bool minorGrid, const bool majorGrid, int xOffset, int yOffset)
   const
{
   UpdateCache( dc, nullptr );
   auto &cache = *mpCache;

   int gridPos;
   wxPen gridPen;

   if(mbMinor && (minorGrid && (gridLineLength != 0 ))) {
      gridPen.SetColour(178, 178, 178); // very light grey
      dc.SetPen(gridPen);
      for( const auto &label : cache.mMinorLabels ) {
         gridPos = label.pos;
         if(mOrientation == wxHORIZONTAL) {
            if((gridPos != 0) && (gridPos != gridLineLength))
               AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset);
         }
         else {
            if((gridPos != 0) && (gridPos != gridLineLength))
               AColor::Line(dc, xOffset, gridPos+yOffset, gridLineLength-1+xOffset, gridPos+yOffset);
         }
      }
   }

   if(majorGrid && (gridLineLength != 0 )) {
      gridPen.SetColour(127, 127, 127); // light grey
      dc.SetPen(gridPen);
      for( const auto &label : cache.mMajorLabels ) {
         gridPos = label.pos;
         if(mOrientation == wxHORIZONTAL) {
            if((gridPos != 0) && (gridPos != gridLineLength))
               AColor::Line(dc, gridPos+xOffset, yOffset, gridPos+xOffset, gridLineLength-1+yOffset);
         }
         else {
            if((gridPos != 0) && (gridPos != gridLineLength))
               AColor::Line(dc, xOffset, gridPos+yOffset, gridLineLength-1+xOffset, gridPos+yOffset);
         }
      }

      int zeroPosition = GetZeroPosition();
      if(zeroPosition > 0) {
         // Draw 'zero' grid line in black
         dc.SetPen(*wxBLACK_PEN);
         if(mOrientation == wxHORIZONTAL) {
            if(zeroPosition != gridLineLength)
               AColor::Line(dc, zeroPosition+xOffset, yOffset, zeroPosition+xOffset, gridLineLength-1+yOffset);
         }
         else {
            if(zeroPosition != gridLineLength)
               AColor::Line(dc, xOffset, zeroPosition+yOffset, gridLineLength-1+xOffset, zeroPosition+yOffset);
         }
      }
   }
}

int Ruler::FindZero( const Labels &labels ) const
{
   auto begin = labels.begin(), end = labels.end(),
      iter = std::find_if( begin, end, []( const Label &label ){
         return label.value == 0.0;
      } );

   if ( iter == end )
      return -1;
   else
      return iter->pos;
}

int Ruler::GetZeroPosition() const
{
   wxASSERT( mpCache );
   auto &cache = *mpCache;
   int zero;
   if( (zero = FindZero( cache.mMajorLabels ) ) < 0)
      zero = FindZero( cache.mMinorLabels );
   // PRL: don't consult minor minor??
   return zero;
}

void Ruler::GetMaxSize(wxCoord *width, wxCoord *height)
{
   if ( !mpCache ) {
      wxScreenDC sdc;
      UpdateCache( sdc, nullptr );
   }

   auto &cache = *mpCache;
   if (width)
      *width = cache.mRect.GetWidth();

   if (height)
      *height = cache.mRect.GetHeight();
}


void Ruler::SetCustomMode(bool value)
{
   if ( mCustom != value ) {
      mCustom = value;
      Invalidate();
   }
}

#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)
{
   SetCustomMode( true );
   mpCache = std::make_unique<Cache>();
   auto &cache = *mpCache;
   auto &mMajorLabels = cache.mMajorLabels;

   const auto numLabel = labels.size();
   mMajorLabels.resize( numLabel );

   for(size_t i = 0; i<numLabel; i++) {
      mMajorLabels[i].text = labels[i];
      mMajorLabels[i].pos  = start + i*step;
   }
}

void Ruler::SetCustomMinorLabels(
   const TranslatableStrings &labels, int start, int step)
{
   SetCustomMode( true );
   mpCache = std::make_unique<Cache>();
   auto &cache = *mpCache;
   auto &mMinorLabels = cache.mMinorLabels;

   const auto numLabel = labels.size();
   mMinorLabels.resize( numLabel );

   for(size_t i = 0; i<numLabel; i++) {
      mMinorLabels[i].text = labels[i];
      mMinorLabels[i].pos  = start + i*step;
   }
}
#endif

void Ruler::Label::Draw(wxDC&dc, bool twoTone, wxColour c) const
{
   if (!text.empty()) {
      bool altColor = twoTone && value < 0.0;

#ifdef EXPERIMENTAL_THEMING
      dc.SetTextForeground(altColor ? theTheme.Colour( clrTextNegativeNumbers) : c);
#else
      dc.SetTextForeground(altColor ? *wxBLUE : *wxBLACK);
#endif
      dc.SetBackgroundMode(wxTRANSPARENT);
      dc.DrawText(text.Translation(), lx, ly);
   }
}

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
//

BEGIN_EVENT_TABLE(RulerPanel, wxPanelWrapper)
   EVT_ERASE_BACKGROUND(RulerPanel::OnErase)
   EVT_PAINT(RulerPanel::OnPaint)
   EVT_SIZE(RulerPanel::OnSize)
END_EVENT_TABLE()

IMPLEMENT_CLASS(RulerPanel, wxPanelWrapper)

RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id,
                       wxOrientation orientation,
                       const wxSize &bounds,
                       const Range &range,
                       Ruler::RulerFormat format,
                       const TranslatableString &units,
                       const Options &options,
                       const wxPoint& pos /*= wxDefaultPosition*/,
                       const wxSize& size /*= wxDefaultSize*/):
   wxPanelWrapper(parent, id, pos, size)
{
   ruler.SetBounds( 0, 0, bounds.x, bounds.y );
   ruler.SetOrientation(orientation);
   ruler.SetRange( range.first, range.second );
   ruler.SetLog( options.log );
   ruler.SetFormat(format);
   ruler.SetUnits( units );
   ruler.SetFlip( options.flip );
   ruler.SetLabelEdges( options.labelEdges );
   ruler.mbTicksAtExtremes = options.ticksAtExtremes;
   if (orientation == wxVERTICAL) {
      wxCoord w;
      ruler.GetMaxSize(&w, NULL);
      SetMinSize(wxSize(w, 150));  // height needed for wxGTK
   }
   else if (orientation == wxHORIZONTAL) {
      wxCoord h;
      ruler.GetMaxSize(NULL, &h);
      SetMinSize(wxSize(wxDefaultCoord, h));
   }
   if (options.hasTickColour)
      ruler.SetTickColour( options.tickColour );
}

RulerPanel::~RulerPanel()
{
}

void RulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
{
   // Ignore it to prevent flashing
}

void RulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
   wxPaintDC dc(this);

#if defined(__WXMSW__)
   dc.Clear();
#endif

   ruler.Draw(dc);
}

void RulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
{
   Refresh();
}

// LL:  We're overloading DoSetSize so that we can update the ruler bounds immediately
//      instead of waiting for a wxEVT_SIZE to come through.  This is needed by (at least)
//      FrequencyPlotDialog since it needs to have an updated ruler before RulerPanel gets the
//      size event.
void RulerPanel::DoSetSize(int x, int y,
                           int width, int height,
                           int sizeFlags)
{
   wxPanelWrapper::DoSetSize(x, y, width, height, sizeFlags);

   int w, h;
   GetClientSize(&w, &h);

   ruler.SetBounds(0, 0, w-1, h-1);
}
