From 54aea4bccf83c2c39c4eee41b725e01656a54d44 Mon Sep 17 00:00:00 2001
From: Paul Licameli <paul.licameli@audacityteam.org>
Date: Tue, 10 Jan 2017 15:53:19 -0500
Subject: [PATCH] Rewrite TrackPanel::FindTrack as FindCell ...

... which reports disjoint rectangles for the track control panel, the vertical
ruler, and the proper track area.
---
 src/TrackPanel.cpp | 308 +++++++++++++++++++++++----------------------
 src/TrackPanel.h   |   9 +-
 2 files changed, 162 insertions(+), 155 deletions(-)

diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp
index b9946ab6a..85e16c780 100644
--- a/src/TrackPanel.cpp
+++ b/src/TrackPanel.cpp
@@ -227,10 +227,10 @@ but that width may be adjusted when tracks change their vertical scales.
 GetLabelWidth() counts columns up to and including the VRuler.
 GetLeftOffset() is yet one more -- it counts the "one pixel" column.
 
-FindTrack() for label returns a rectangle up to and including the One Pixel column,
+FindCell() for label or vruler returns a rectangle up to and including the One Pixel column,
 but OMITS left and top insets
 
-FindTrack() for !label returns a rectangle with x == GetLeftOffset(), and INCLUDES
+FindCell() for track returns a rectangle with x == GetLeftOffset(), and INCLUDES
 right and top insets
 
 +--------------- ... ------ ... --------------------- ...       ... -------------+
@@ -1723,10 +1723,9 @@ void TrackPanel::HandleCursor(const wxMouseEvent & event)
 
    // (2) If we are not over a track at all, set the cursor to Arrow and
    //     clear the StatusBar,
-   wxRect labelRect, trackRect;
-   Track *const label = FindTrack(event.m_x, event.m_y, true, true, &labelRect);
-   Track *const nonlabel = FindTrack(event.m_x, event.m_y, false, false, &trackRect);
-   Track *const track = label ? label : nonlabel;
+   const auto foundCell = FindCell( event.m_x, event.m_y );
+   auto &track = foundCell.pTrack;
+   auto &trackRect = foundCell.rect;
 
    if (!track) {
       SetCursor(*mArrowCursor);
@@ -1745,16 +1744,17 @@ void TrackPanel::HandleCursor(const wxMouseEvent & event)
    wxString tip;
 
    // Are we within the vertical resize area?
-   if (nonlabel
-      ? within(event.m_y, trackRect.y + trackRect.height, TRACK_RESIZE_REGION)
-      : within(event.m_y, labelRect.y + labelRect.height, TRACK_RESIZE_REGION))
+   if (within(event.m_y, trackRect.GetBottom(), TRACK_RESIZE_REGION))
    {
-      SetCursorAndTipWhenInVResizeArea(nonlabel && track->GetLinked(), tip);
+      SetCursorAndTipWhenInVResizeArea(
+         track->GetLinked() && foundCell.type != CellType::Label, tip);
       // tip may still be NULL at this point, in which case we go on looking.
    }
 
-   if ((tip == wxString()) && label) {
-      SetCursorAndTipWhenInLabel( label, event, tip );
+   if ((tip == wxString()) &&
+       (foundCell.type == CellType::Label ||
+        foundCell.type == CellType::VRuler)) {
+      SetCursorAndTipWhenInLabel( track, event, tip );
    }
 
    // Otherwise, we must be over a track of some kind
@@ -1768,9 +1768,8 @@ void TrackPanel::HandleCursor(const wxMouseEvent & event)
    }
 
    if ((tip == wxString()) &&
-      nonlabel &&
-      nonlabel->GetKind() == Track::Wave &&
-      SetCursorForCutline(static_cast<WaveTrack*>(nonlabel), trackRect, event))
+      track->GetKind() == Track::Wave &&
+      SetCursorForCutline(static_cast<WaveTrack*>(track), trackRect, event))
       return;
 
    if( tip == wxString() )
@@ -1818,8 +1817,9 @@ void TrackPanel::HandleCursor(const wxMouseEvent & event)
 /// dragging over a waveform.
 void TrackPanel::HandleSelect(wxMouseEvent & event)
 {
-   wxRect rect;
-   Track *t = FindTrack(event.m_x, event.m_y, false, false, &rect);
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto &t = foundCell.pTrack;
+   auto &rect = foundCell.rect;
 
    // AS: Ok, did the user just click the mouse, release the mouse,
    //  or drag?
@@ -1849,9 +1849,7 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
 
    } else if (event.LeftDClick() && !event.ShiftDown()) {
       if (!mCapturedTrack) {
-         wxRect rect;
-         mCapturedTrack =
-            FindTrack(event.m_x, event.m_y, false, false, &rect);
+         mCapturedTrack = t;
          if (!mCapturedTrack)
             return;
       }
@@ -2894,12 +2892,6 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
    wxRect rect      = mCapturedRect;
    Track *pTrack = mCapturedTrack;
 
-   if (!pTrack) {
-      pTrack = FindTrack(event.m_x, event.m_y, false, false, &rect);
-      rect.y += kTopMargin;
-      rect.height -= kTopMargin + kBottomMargin;
-   }
-
    // Also fuhggeddaboudit if not in a track.
    if (!pTrack)
       return;
@@ -2928,7 +2920,7 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
 
    // Handle which tracks are selected
    Track *sTrack = pTrack;
-   Track *eTrack = FindTrack(x, y, false, false, NULL);
+   Track *eTrack = FindCell(x, y).pTrack;
    if( !event.ControlDown() )
       SelectRangeOfTracks(sTrack, eTrack);
 
@@ -3165,10 +3157,11 @@ bool mayDragWidth, bool onlyWithinSnapDistance,
 void TrackPanel::HandleEnvelope(wxMouseEvent & event)
 {
    if (event.LeftDown()) {
-      wxRect rect;
-      Track *pTrack = FindTrack(event.m_x, event.m_y, false, false, &rect);
+      const auto foundCell = FindCell(event.m_x, event.m_y);
+      auto &pTrack = foundCell.pTrack;
+      auto &rect = foundCell.rect;
 
-      if (!pTrack)
+      if (!pTrack || foundCell.type != CellType::Track)
          return;
 
       SetCapturedTrack(pTrack, IsEnveloping);
@@ -3437,16 +3430,16 @@ namespace {
 /// Prepare for sliding.
 void TrackPanel::StartSlide(wxMouseEvent & event)
 {
-   wxRect rect;
-
    mHSlideAmount = 0.0;
    mDidSlideVertically = false;
 
    mTrackExclusions.clear();
 
-   Track *vt = FindTrack(event.m_x, event.m_y, false, false, &rect);
-   if (!vt)
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto &vt = foundCell.pTrack;
+   if (!vt || foundCell.type != CellType::Track)
       return;
+   auto &rect = foundCell.rect;
 
    ToolsToolBar * ttb = mListener->TP_GetToolsToolBar();
    bool multiToolModeActive = (ttb && ttb->IsDown(multiTool));
@@ -3657,16 +3650,17 @@ void TrackPanel::DoSlide(wxMouseEvent & event)
 
    // find which track the mouse is currently in (mouseTrack) -
    // this may not be the same as the one we started in...
-   Track *mouseTrack = FindTrack(event.m_x, event.m_y, false, false, NULL);
-   if (mouseTrack == NULL) {
-      // Allow sliding if the pointer is not over any track, but only if x is
+
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   if (foundCell.type != CellType::Track)
+      // Allow sliding only if x is
       // within the bounds of the tracks area.
-      int width;
-      GetTracksUsableArea(&width, NULL);
-      if (event.m_x >= GetLeftOffset() && event.m_x < GetLeftOffset() + width)
-         mouseTrack = mCapturedTrack;
-      else
-         return;
+      return;
+
+   auto mouseTrack = foundCell.pTrack;
+   if (mouseTrack == nullptr) {
+      // Allow sliding if the pointer is not over any track.
+      mouseTrack = mCapturedTrack;
    }
 
    // Start by undoing the current slide amount; everything
@@ -3987,9 +3981,10 @@ void TrackPanel::HandleZoomClick(wxMouseEvent & event)
    if (mCapturedTrack)
       return;
 
-   mCapturedTrack = FindTrack(event.m_x, event.m_y, false, false,
-                              &mCapturedRect);
-   if (!mCapturedTrack)
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   mCapturedTrack = foundCell.pTrack;
+   mCapturedRect = foundCell.rect;
+   if (foundCell.type != CellType::Track || !(mCapturedTrack = foundCell.pTrack))
       return;
 
    SetCapturedTrack(mCapturedTrack, IsZooming);
@@ -4106,9 +4101,10 @@ void TrackPanel::HandleVZoomClick( wxMouseEvent & event )
 {
    if (mCapturedTrack)
       return;
-   mCapturedTrack = FindTrack(event.m_x, event.m_y, true, false,
-                              &mCapturedRect);
-   if (!mCapturedTrack)
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   mCapturedTrack = foundCell.pTrack;
+   mCapturedRect = foundCell.rect;
+   if (foundCell.type != CellType::VRuler || !(mCapturedTrack = foundCell.pTrack))
       return;
 
    if (mCapturedTrack->GetKind() == Track::Wave
@@ -4458,10 +4454,11 @@ bool TrackPanel::IsSampleEditingPossible( wxMouseEvent &event, const WaveTrack *
 
    bool showPoints;
    {
-      wxRect rect;
-      FindTrack(event.m_x, event.m_y, false, false, &rect);
+      const auto foundCell = FindCell(event.m_x, event.m_y);
+      if ( foundCell.type != CellType::Track )
+         return false;
       const double rate = wt->GetRate();
-      const double time = mViewInfo->PositionToTime(event.m_x, rect.x);
+      const double time = mViewInfo->PositionToTime(event.m_x, foundCell.rect.x);
       int width;
       GetTracksUsableArea(&width, NULL);
       showPoints = SampleResolutionTest(*mViewInfo, wt, time, rate, width);
@@ -4509,14 +4506,14 @@ float TrackPanel::FindSampleEditingLevel(wxMouseEvent &event, double dBRange, do
 /// Someone has just clicked the mouse.  What do we do?
 void TrackPanel::HandleSampleEditingClick( wxMouseEvent & event )
 {
-   //declare a rectangle to determine clicking position
-   wxRect rect;
-
    //Get the track the mouse is over, and save it away for future events
    mDrawingTrack = NULL;
-   Track *const t = FindTrack(event.m_x, event.m_y, false, false, &rect);
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto &t = foundCell.pTrack;
+   auto &rect = foundCell.rect;
 
-   if (!t || (t->GetKind() != Track::Wave))
+   if (!t || (t->GetKind() != Track::Wave)
+       || foundCell.type != CellType::Track)
       return;
    const auto wt = static_cast<WaveTrack *>(t);
    if( !IsSampleEditingPossible( event, wt ) )
@@ -5062,9 +5059,9 @@ void TrackPanel::HandleLabelClick(wxMouseEvent & event)
 
    bool unsafe = IsUnsafe();
 
-   wxRect rect;
-
-   Track *t = FindTrack(event.m_x, event.m_y, true, true, &rect);
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto &t = foundCell.pTrack;
+   auto &rect = foundCell.rect;
 
    // LL: Check close box
    if (isleft && CloseFunc(t, rect, event.m_x, event.m_y))
@@ -5384,20 +5381,14 @@ bool TrackPanel::PopupFunc(Track * t, wxRect rect, int x, int y)
 ///  update the track size.
 void TrackPanel::HandleResizeClick( wxMouseEvent & event )
 {
-   wxRect rTrack;
-   wxRect rLabel;
-
+   // Get here only if the click was near the bottom of the cell rectangle.
    // DM: Figure out what track is about to be resized
-   Track *track = FindTrack(event.m_x, event.m_y, false, false, &rTrack);
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto track = foundCell.pTrack;
 
-   if (!track) {
-      // This will only return unlinked tracks or left channels of stereo tracks
-      // or NULL:
-      track = FindTrack(event.m_x, event.m_y, true, true, &rLabel);
-      // If stereo, get the right channel.
-      if (track && track->GetLinked())
-         track = track->GetLink();
-   }
+   if (foundCell.type == CellType::Label && track && track->GetLinked())
+      // Click was at the bottom of a stereo track.
+      track = track->GetLink();
 
    if (!track) {
       return;
@@ -5695,9 +5686,10 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
 
    // Special case of pointer in the vertical ruler
    if (event.ShiftDown() || event.CmdDown()) {
-      wxRect rect;
-      Track *const pTrack = FindTrack(event.m_x, event.m_y, true, false, &rect);
-      if (pTrack && event.m_x >= GetVRulerOffset()) {
+      const auto foundCell = FindCell(event.m_x, event.m_y);
+      auto &pTrack = foundCell.pTrack;
+      auto &rect = foundCell.rect;
+      if (pTrack && foundCell.type == CellType::VRuler) {
          HandleWheelRotationInVRuler(event, steps, pTrack, rect);
          // Always stop propagation even if the ruler didn't change.  The ruler
          // is a narrow enough target.
@@ -6213,8 +6205,11 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event)
    if (event.ButtonUp()) {
       wxRect rect;
 
-      Track *t = FindTrack(event.m_x, event.m_y, false, false, &rect);
-      if (t)
+      const auto foundCell = FindCell(event.m_x, event.m_y);
+      auto t = foundCell.pTrack;
+      if (t
+          && foundCell.type == CellType::Track
+      )
          EnsureVisible(t);
    }
 }
@@ -6544,21 +6539,16 @@ void TrackPanel::HandleTextDragRelease(LabelTrack * lTrack, wxMouseEvent & event
 //  from the other OnMouseEvent code.
 void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event)
 {
-   Track * pTrack;
-   Track * pControlTrack;
-   wxRect rTrack;
-   wxRect rLabel;
+   const auto foundCell = FindCell( event.m_x, event.m_y );
+   auto &pTrack = foundCell.pTrack;
+   auto &rect = foundCell.rect;
 
    bool unsafe = IsUnsafe();
 
-   pControlTrack = FindTrack(event.m_x, event.m_y, true, true, &rLabel);
-   pTrack = FindTrack(event.m_x, event.m_y, false, false, &rTrack);
-
    //call HandleResize if I'm over the border area
    if (event.LeftDown() &&
-          (within(event.m_y, rTrack.y + rTrack.height, TRACK_RESIZE_REGION)
-        || within(event.m_y, rLabel.y + rLabel.height,
-                  TRACK_RESIZE_REGION))) {
+       pTrack &&
+          (within(event.m_y, rect.GetBottom(), TRACK_RESIZE_REGION))) {
       HandleResize(event);
       HandleCursor(event);
       return;
@@ -6566,43 +6556,45 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event)
 
    // AS: If the user clicked outside all tracks, make nothing
    //  selected.
-   if ((event.ButtonDown() || event.ButtonDClick()) &&
-       !(pTrack || pControlTrack)) {
+   if ((event.ButtonDown() || event.ButtonDClick()) && !pTrack) {
       SelectNone();
       Refresh(false);
       return;
    }
 
-   //Determine if user clicked on the track's left-hand label
-   if (!mCapturedTrack && event.m_x < GetLeftOffset()) {
-      if (event.m_x >= GetVRulerOffset()) {
+   //Determine if user clicked on the track's left-hand label or ruler
+   if (!mCapturedTrack) {
+      if (foundCell.type == CellType::VRuler) {
          if( !event.Dragging() ) // JKC: Only want the mouse down event.
             HandleVZoom(event);
          HandleCursor(event);
+         return;
       }
-      else {
+      else if (foundCell.type == CellType::Label) {
          HandleLabelClick(event);
          HandleCursor(event);
+         return;
       }
-      return;
    }
 
    //Determine if user clicked on a label track.
    //If so, use MouseDown handler for the label track.
-   if (pTrack && (pTrack->GetKind() == Track::Label))
+   if (pTrack && foundCell.type == CellType::Track &&
+       (pTrack->GetKind() == Track::Label))
    {
-      if (HandleLabelTrackClick((LabelTrack *)pTrack, rTrack, event))
+      if (HandleLabelTrackClick((LabelTrack *)pTrack, rect, event))
          return;
    }
 
    bool handled = false;
 
-   if (pTrack && (pTrack->GetKind() == Track::Wave) &&
+   if (pTrack && foundCell.type == CellType::Track &&
+       (pTrack->GetKind() == Track::Wave) &&
       (mMouseCapture == IsUncaptured || mMouseCapture == WasOverCutLine))
-      handled = HandleTrackLocationMouseEvent((WaveTrack *)pTrack, rTrack, event);
+      handled = HandleTrackLocationMouseEvent((WaveTrack *)pTrack, rect, event);
 
    ToolsToolBar * pTtb = mListener->TP_GetToolsToolBar();
-   if( !handled && pTtb != NULL )
+   if( !handled && pTtb != NULL && foundCell.type == CellType::Track )
    {
       int toolToUse = DetermineToolToUse(pTtb, event);
 
@@ -6665,10 +6657,10 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & ev
       return currentTool;
 
    // So now we have to find out what we are near to..
-   wxRect rect;
-
-   Track *pTrack = FindTrack(event.m_x, event.m_y, false, false, &rect);
-   if( !pTrack )
+   const auto foundCell = FindCell(event.m_x, event.m_y);
+   auto &pTrack = foundCell.pTrack;
+   auto &rect = foundCell.rect;
+   if( !pTrack|| foundCell.type != CellType::Track )
       return currentTool;
 
    int trackKind = pTrack->GetKind();
@@ -8790,91 +8782,101 @@ void TrackPanel::OnSetFont(wxCommandEvent & WXUNUSED(event))
 /// Determines which track is under the mouse
 ///  @param mouseX - mouse X position.
 ///  @param mouseY - mouse Y position.
-///  @param label  - true iff the X Y position is relative to side-panel with the labels in it.
-///  @param link - true iff we should consider a hit in any linked track as a hit.
-///  @param *trackRect - returns track rectangle.
-Track *TrackPanel::FindTrack(int mouseX, int mouseY, bool label, bool link,
-                              wxRect * trackRect)
+TrackPanel::FoundCell TrackPanel::FindCell(int mouseX, int mouseY)
 {
-   // If label is true, resulting rectangle OMITS left and top insets.
-   // If label is false, resulting rectangle INCLUDES right and top insets.
-
    wxRect rect;
    rect.x = 0;
    rect.y = -mViewInfo->vpos;
    rect.y += kTopInset;
    GetSize(&rect.width, &rect.height);
 
-   if (label) {
-      rect.width = GetLeftOffset();
-   } else {
-      rect.x = GetLeftOffset();
-      rect.width -= GetLeftOffset();
+   // The type of cell that may be found is determined by the x coordinate.
+   CellType type = CellType::Track;
+   if (mouseX >= 0 && mouseX < rect.width) {
+      if (mouseX < GetVRulerOffset())
+         type = CellType::Label,
+         rect.width = GetVRulerOffset();
+      else if (mouseX < GetLeftOffset())
+         type = CellType::VRuler,
+         rect.x = GetVRulerOffset(),
+         rect.width = GetLeftOffset() - GetVRulerOffset();
+      else
+         type = CellType::Track,
+         rect.x = GetLeftOffset(),
+         rect.width -= GetLeftOffset();
    }
 
+   auto output = [&](Track *pTrack) -> FoundCell {
+      // If label or vruler, resulting rectangle OMITS left and top insets.
+      // If track, resulting rectangle INCLUDES right and top insets.
+      if (pTrack) {
+         rect.y -= kTopInset;
+         switch (type) {
+            case CellType::Label:
+               rect.x += kLeftInset;
+               rect.width -= kLeftInset;
+               rect.y += kTopInset;
+               rect.height -= kTopInset;
+               break;
+            case CellType::VRuler:
+               rect.y += kTopInset;
+               rect.height -= kTopInset;
+               break;
+            case CellType::Track:
+            default:
+               break;
+         }
+         return { pTrack, type, rect };
+      }
+      else
+         return { nullptr, type, {} };
+   };
+
    VisibleTrackIterator iter(GetProject());
    for (Track * t = iter.First(); t; t = iter.Next()) {
       rect.y = t->GetY() - mViewInfo->vpos + kTopInset;
       rect.height = t->GetHeight();
 
-      if (link && t->GetLink()) {
-         Track *l = t->GetLink();
-         int h = l->GetHeight();
-         if (!t->GetLinked()) {
-            t = l;
-            rect.y = t->GetY() - mViewInfo->vpos + kTopInset;
+      if (type == CellType::Label) {
+         if (t->GetLink()) {
+            Track *l = t->GetLink();
+            int h = l->GetHeight();
+            if (!t->GetLinked()) {
+               t = l;
+               rect.y = t->GetY() - mViewInfo->vpos + kTopInset;
+            }
+            rect.height += h;
          }
-         rect.height += h;
-      }
 #ifdef EXPERIMENTAL_OUTPUT_DISPLAY
-      else if(link && MONO_WAVE_PAN(t))
-      {
-         rect.height += t->GetHeight(true);
-      }
+         else if( MONO_WAVE_PAN(t) )
+            rect.height += t->GetHeight(true);
 #endif
+      }
+
       //Determine whether the mouse is inside
       //the current rectangle.  If so, recalculate
       //the proper dimensions and return.
       if (rect.Contains(mouseX, mouseY)) {
 #ifdef EXPERIMENTAL_OUTPUT_DISPLAY
+         // PRL:  Is it good to have a side effect in a hit-testing routine?
          t->SetVirtualStereo(false);
 #endif
-         if (trackRect) {
-            rect.y -= kTopInset;
-            if (label) {
-               rect.x += kLeftInset;
-               rect.width -= kLeftInset;
-               rect.y += kTopInset;
-               rect.height -= kTopInset;
-            }
-            *trackRect = rect;
-         }
-
-         return t;
+         return output(t);
       }
 #ifdef EXPERIMENTAL_OUTPUT_DISPLAY
-      if(!link && MONO_WAVE_PAN(t)){
+      if(type != CellType::Label && MONO_WAVE_PAN(t)){
          rect.y = t->GetY(true) - mViewInfo->vpos + kTopInset;
          rect.height = t->GetHeight(true);
          if (rect.Contains(mouseX, mouseY)) {
+            // PRL:  Is it good to have a side effect in a hit-testing routine?
             t->SetVirtualStereo(true);
-            if (trackRect) {
-               rect.y -= kTopInset;
-               if (label) {
-                  rect.x += kLeftInset;
-                  rect.width -= kLeftInset;
-                  rect.y += kTopInset;
-                  rect.height -= kTopInset;
-               }
-               *trackRect = rect;
-            }
-            return t;
+            return output(t);
          }
       }
 #endif // EXPERIMENTAL_OUTPUT_DISPLAY
    }
 
-   return NULL;
+   return { nullptr, type, {} };
 }
 
 /// This finds the rectangle of a given track, either the
diff --git a/src/TrackPanel.h b/src/TrackPanel.h
index a9958faf2..6adc12be8 100644
--- a/src/TrackPanel.h
+++ b/src/TrackPanel.h
@@ -479,8 +479,13 @@ protected:
    virtual void OnMergeStereo(wxCommandEvent &event);
 
    // Find track info by coordinate
-   virtual Track *FindTrack(int mouseX, int mouseY, bool label, bool link,
-                     wxRect * trackRect = NULL);
+   enum class CellType { Label, Track, VRuler };
+   struct FoundCell {
+      Track *pTrack;
+      CellType type;
+      wxRect rect;
+   };
+   virtual FoundCell FindCell(int mouseX, int mouseY);
 
    virtual wxRect FindTrackRect(Track * target, bool label);