Audacity  2.2.2
TrackPanel.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  TrackPanel.cpp
6 
7  Dominic Mazzoni
8  and lots of other contributors
9 
10  Implements TrackPanel and TrackInfo.
11 
12 ********************************************************************//*****************************************************************//********************************************************************/
34 
35 // Documentation: Rather than have a lengthy \todo section, having
36 // a \todo a \file and a \page in EXACTLY that order gets Doxygen to
37 // put the following lengthy description of refactoring on a NEW page
38 // and link to it from the docs.
39 
40 /*****************************************************************//*****************************************************************//**************************************************************//**************************************************************//**************************************************************//**************************************************************//**************************************************************//*****************************************************************//*****************************************************************/
153 
154 #include "Audacity.h"
155 #include "Experimental.h"
156 #include "TrackPanel.h"
157 #include "Project.h"
158 #include "TrackPanelCellIterator.h"
159 #include "TrackPanelMouseEvent.h"
160 #include "TrackPanelResizeHandle.h"
161 //#define DEBUG_DRAW_TIMING 1
162 
163 #include "AColor.h"
164 #include "AllThemeResources.h"
165 #include "AudioIO.h"
166 #include "float_cast.h"
167 
168 #include "Prefs.h"
169 #include "RefreshCode.h"
170 #include "TrackArtist.h"
171 #include "TrackPanelAx.h"
172 #include "UIHandle.h"
173 #include "HitTestResult.h"
174 #include "WaveTrack.h"
175 #ifdef EXPERIMENTAL_MIDI_OUT
176 #include "NoteTrack.h"
177 #endif
178 
179 #include "toolbars/ControlToolBar.h"
180 #include "toolbars/ToolsToolBar.h"
181 
182 //This loads the appropriate set of cursors, depending on platform.
183 #include "../images/Cursors.h"
184 
185 #include "widgets/ASlider.h"
186 #include "widgets/Ruler.h"
187 #include <algorithm>
188 
189 DEFINE_EVENT_TYPE(EVT_TRACK_PANEL_TIMER)
190 
191 /*
192 
193 This is a diagram of TrackPanel's division of one (non-stereo) track rectangle.
194 Total height equals Track::GetHeight()'s value. Total width is the wxWindow's width.
195 Each charater that is not . represents one pixel.
196 
197 Inset space of this track, and top inset of the next track, are used to draw the focus highlight.
198 
199 Top inset of the right channel of a stereo track, and bottom shadow line of the
200 left channel, are used for the channel separator.
201 
202 "Margin" is a term used for inset plus border (top and left) or inset plus
203 shadow plus border (right and bottom).
204 
205 TrackInfo::GetTrackInfoWidth() == GetVRulerOffset()
206 counts columns from the left edge up to and including controls, and is a constant.
207 
208 GetVRulerWidth() is variable -- all tracks have the same ruler width at any time,
209 but that width may be adjusted when tracks change their vertical scales.
210 
211 GetLabelWidth() counts columns up to and including the VRuler.
212 GetLeftOffset() is yet one more -- it counts the "one pixel" column.
213 
214 FindCell() for label returns a rectangle that OMITS left, top, and bottom
215 margins
216 
217 FindCell() for vruler returns a rectangle right of the label,
218 up to and including the One Pixel column, and OMITS top and bottom margins
219 
220 FindCell() for track returns a rectangle with x == GetLeftOffset(), and OMITS
221 right top, and bottom margins
222 
223 +--------------- ... ------ ... --------------------- ... ... -------------+
224 | Top Inset |
225 | |
226 | +------------ ... ------ ... --------------------- ... ... ----------+ |
227 | L|+-Border---- ... ------ ... --------------------- ... ... -Border-+ |R |
228 | e||+---------- ... -++--- ... -+++----------------- ... ... -------+| |i |
229 | f|B| || ||| |BS|g |
230 | t|o| Controls || V |O| The good stuff |oh|h |
231 | |r| || R |n| |ra|t |
232 | I|d| || u |e| |dd| |
233 | n|e| || l | | |eo|I |
234 | s|r| || e |P| |rw|n |
235 | e||| || r |i| ||||s |
236 | t||| || |x| ||||e |
237 | ||| || |e| ||||t |
238 | ||| || |l| |||| |
239 | ||| || ||| |||| |
240 
241 . ... .. ... .... .
242 . ... .. ... .... .
243 . ... .. ... .... .
244 
245 | ||| || ||| |||| |
246 | ||+---------- -++-- ... -+++----------------- ... ... -------+||| |
247 | |+-Border---- ... ----- ... --------------------- ... ... -Border-+|| |
248 | | Shadow---- ... ----- ... --------------------- ... ... --Shadow-+| |
249 */
250 
251 // Is the distance between A and B less than D?
252 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
253 {
254  return (a > b - d) && (a < b + d);
255 }
256 
257 BEGIN_EVENT_TABLE(TrackPanel, OverlayPanel)
258  EVT_MOUSE_EVENTS(TrackPanel::OnMouseEvent)
259  EVT_MOUSE_CAPTURE_LOST(TrackPanel::OnCaptureLost)
260  EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, TrackPanel::OnCaptureKey)
261  EVT_KEY_DOWN(TrackPanel::OnKeyDown)
262  EVT_KEY_UP(TrackPanel::OnKeyUp)
263  EVT_CHAR(TrackPanel::OnChar)
264  EVT_PAINT(TrackPanel::OnPaint)
265  EVT_SET_FOCUS(TrackPanel::OnSetFocus)
266  EVT_KILL_FOCUS(TrackPanel::OnKillFocus)
267  EVT_CONTEXT_MENU(TrackPanel::OnContextMenu)
268 
269  EVT_TIMER(wxID_ANY, TrackPanel::OnTimer)
271 
274 std::unique_ptr<wxCursor> MakeCursor( int WXUNUSED(CursorId), const char * pXpm[36], int HotX, int HotY )
275 {
276 #ifdef CURSORS_SIZE32
277  const int HotAdjust =0;
278 #else
279  const int HotAdjust =8;
280 #endif
281 
282  wxImage Image = wxImage(wxBitmap(pXpm).ConvertToImage());
283  Image.SetMaskColour(255,0,0);
284  Image.SetMask();// Enable mask.
285 
286  Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_X, HotX-HotAdjust );
287  Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_Y, HotY-HotAdjust );
288  return std::make_unique<wxCursor>( Image );
289 }
290 
291 
292 
293 // Don't warn us about using 'this' in the base member initializer list.
294 #ifndef __WXGTK__ //Get rid if this pragma for gtk
295 #pragma warning( disable: 4355 )
296 #endif
297 TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
298  const wxPoint & pos,
299  const wxSize & size,
300  const std::shared_ptr<TrackList> &tracks,
301  ViewInfo * viewInfo,
302  TrackPanelListener * listener,
303  AdornedRulerPanel * ruler)
304  : OverlayPanel(parent, id, pos, size, wxWANTS_CHARS | wxNO_BORDER),
305  mTrackInfo(this),
306  mListener(listener),
307  mTracks(tracks),
308  mViewInfo(viewInfo),
309  mRuler(ruler),
310  mTrackArtist(nullptr),
311  mRefreshBacking(false),
312  vrulerSize(36,0)
313 #ifndef __WXGTK__ //Get rid if this pragma for gtk
314 #pragma warning( default: 4355 )
315 #endif
316 {
317  SetLabel(_("Track Panel"));
318  SetName(_("Track Panel"));
319  SetBackgroundStyle(wxBG_STYLE_PAINT);
320 
321  {
322  auto pAx = std::make_unique <TrackPanelAx>( this );
323 #if wxUSE_ACCESSIBILITY
324  // wxWidgets owns the accessible object
325  SetAccessible(mAx = pAx.release());
326 #else
327  // wxWidgets does not own the object, but we need to retain it
328  mAx = std::move(pAx);
329 #endif
330  }
331 
332  mRedrawAfterStop = false;
333 
334  mTrackArtist = std::make_unique<TrackArtist>();
335 
337 
338  mTimeCount = 0;
339  mTimer.parent = this;
340  // Timer is started after the window is visible
341  GetProject()->Bind(wxEVT_IDLE, &TrackPanel::OnIdle, this);
342 
343  // Register for tracklist updates
344  mTracks->Connect(EVT_TRACKLIST_RESIZING,
345  wxCommandEventHandler(TrackPanel::OnTrackListResizing),
346  NULL,
347  this);
348  mTracks->Connect(EVT_TRACKLIST_DELETION,
349  wxCommandEventHandler(TrackPanel::OnTrackListDeletion),
350  NULL,
351  this);
352  wxTheApp->Connect(EVT_AUDIOIO_PLAYBACK,
353  wxCommandEventHandler(TrackPanel::OnPlayback),
354  NULL,
355  this);
356 }
357 
358 
360 {
361  mTimer.Stop();
362 
363  // Unregister for tracklist updates
364  mTracks->Disconnect(EVT_TRACKLIST_DELETION,
365  wxCommandEventHandler(TrackPanel::OnTrackListDeletion),
366  NULL,
367  this);
368  mTracks->Disconnect(EVT_TRACKLIST_RESIZING,
369  wxCommandEventHandler(TrackPanel::OnTrackListResizing),
370  NULL,
371  this);
372  wxTheApp->Disconnect(EVT_AUDIOIO_PLAYBACK,
373  wxCommandEventHandler(TrackPanel::OnPlayback),
374  NULL,
375  this);
376 
377  // This can happen if a label is being edited and the user presses
378  // ALT+F4 or Command+Q
379  if (HasCapture())
380  ReleaseMouse();
381 }
382 
384 {
385  auto rect = FindTrackRect( wt, true );
386  wxRect sliderRect;
387  TrackInfo::GetGainRect( rect.GetTopLeft(), sliderRect );
388  return TrackInfo::GainSlider(sliderRect, wt, false, this);
389 }
390 
392 {
393  auto rect = FindTrackRect( wt, true );
394  wxRect sliderRect;
395  TrackInfo::GetPanRect( rect.GetTopLeft(), sliderRect );
396  return TrackInfo::PanSlider(sliderRect, wt, false, this);
397 }
398 
399 #ifdef EXPERIMENTAL_MIDI_OUT
400 LWSlider *TrackPanel::VelocitySlider( const NoteTrack *nt )
401 {
402  auto rect = FindTrackRect( nt, true );
403  wxRect sliderRect;
404  TrackInfo::GetVelocityRect( rect.GetTopLeft(), sliderRect );
405  return TrackInfo::VelocitySlider(sliderRect, nt, false, this);
406 }
407 #endif
408 
409 wxString TrackPanel::gSoloPref;
410 
412 {
413  gPrefs->Read(wxT("/GUI/AutoScroll"), &mViewInfo->bUpdateTrackIndicator,
414  true);
415  gPrefs->Read(wxT("/GUI/Solo"), &gSoloPref, wxT("Simple"));
416 
418 
419  if (mTrackArtist) {
420  mTrackArtist->UpdatePrefs();
421  }
422 
423  // All vertical rulers must be recalculated since the minimum and maximum
424  // frequences may have been changed.
425  UpdateVRulers();
426 
428 
429  Refresh();
430 }
431 
433 {
435 }
436 
437 
438 void TrackPanel::GetTracksUsableArea(int *width, int *height) const
439 {
440  GetSize(width, height);
441  if (width) {
442  *width -= GetLeftOffset();
443  *width -= kRightMargin;
444  *width = std::max(0, *width);
445  }
446 }
447 
451 {
452  //JKC casting away constness here.
453  //Do it in two stages in case 'this' is not a wxWindow.
454  //when the compiler will flag the error.
455  wxWindow const * const pConstWind = this;
456  wxWindow * pWind=(wxWindow*)pConstWind;
457 #ifdef EXPERIMENTAL_NOTEBOOK
458  pWind = pWind->GetParent(); //Page
459  wxASSERT( pWind );
460  pWind = pWind->GetParent(); //Notebook
461  wxASSERT( pWind );
462 #endif
463  pWind = pWind->GetParent(); //MainPanel
464  wxASSERT( pWind );
465  pWind = pWind->GetParent(); //Project
466  wxASSERT( pWind );
467  return (AudacityProject*)pWind;
468 }
469 
470 void TrackPanel::OnIdle(wxIdleEvent& event)
471 {
472  // The window must be ready when the timer fires (#1401)
473  if (IsShownOnScreen())
474  {
475  mTimer.Start(kTimerInterval, FALSE);
476 
477  // Timer is started, we don't need the event anymore
478  GetProject()->Unbind(wxEVT_IDLE, &TrackPanel::OnIdle, this);
479  }
480  else
481  {
482  // Get another idle event, wx only guarantees we get one
483  // event after "some other normal events occur"
484  event.RequestMore();
485  }
486 }
487 
489 void TrackPanel::OnTimer(wxTimerEvent& )
490 {
491 #ifdef __WXMAC__
492  // Unfortunate part of fix for bug 1431
493  // Without this, the toolbars hide only every other time that you press
494  // the yellow title bar button. For some reason, not every press sends
495  // us a deactivate event for the application.
496  {
497  auto project = GetProject();
498  if (project->IsIconized())
499  project->MacShowUndockedToolbars(false);
500  }
501 #endif
502 
503  mTimeCount++;
504 
505  AudacityProject *const p = GetProject();
506 
507  // Check whether we were playing or recording, but the stream has stopped.
508  if (p->GetAudioIOToken()>0 && !IsAudioActive())
509  {
510  //the stream may have been started up after this one finished (by some other project)
511  //in that case reset the buttons don't stop the stream
513  }
514 
515  // Next, check to see if we were playing or recording
516  // audio, but now Audio I/O is completely finished.
517  if (p->GetAudioIOToken()>0 &&
519  {
520  p->FixScrollbars();
521  p->SetAudioIOToken(0);
522  p->RedrawProject();
523 
524  mRedrawAfterStop = false;
525 
526  //ANSWER-ME: Was DisplaySelection added to solve a repaint problem?
528  }
531  }
532 
533  // Notify listeners for timer ticks
534  {
535  wxCommandEvent e(EVT_TRACK_PANEL_TIMER);
536  p->GetEventHandler()->ProcessEvent(e);
537  }
538 
539  DrawOverlays(false);
540  mRuler->DrawOverlays(false);
541 
543 
544  // Periodically update the display while recording
545 
546  if (!mRedrawAfterStop) {
547  mRedrawAfterStop = true;
549  mListener->TP_ScrollUpDown( 99999999 );
550  Refresh( false );
551  }
552  else {
553  if ((mTimeCount % 5) == 0) {
554  // Must tell OnPaint() to recreate the backing bitmap
555  // since we've not done a full refresh.
556  mRefreshBacking = true;
557  Refresh( false );
558  }
559  }
560  }
561  if(mTimeCount > 1000)
562  mTimeCount = 0;
563 }
564 
566 {
567  int width;
568  GetTracksUsableArea(&width, NULL);
569  return mViewInfo->PositionToTime(width, 0, true);
570 }
571 
574 void TrackPanel::OnPaint(wxPaintEvent & /* event */)
575 {
577 
578 #if DEBUG_DRAW_TIMING
579  wxStopWatch sw;
580 #endif
581 
582  {
583  wxPaintDC dc(this);
584 
585  // Retrieve the damage rectangle
586  wxRect box = GetUpdateRegion().GetBox();
587 
588  // Recreate the backing bitmap if we have a full refresh
589  // (See TrackPanel::Refresh())
590  if (mRefreshBacking || (box == GetRect()))
591  {
592  // Reset (should a mutex be used???)
593  mRefreshBacking = false;
594 
595  // Redraw the backing bitmap
597 
598  // Copy it to the display
599  DisplayBitmap(dc);
600  }
601  else
602  {
603  // Copy full, possibly clipped, damage rectangle
604  RepairBitmap(dc, box.x, box.y, box.width, box.height);
605  }
606 
607  // Done with the clipped DC
608 
609  // Drawing now goes directly to the client area.
610  // DrawOverlays() may need to draw outside the clipped region.
611  // (Used to make a NEW, separate wxClientDC, but that risks flashing
612  // problems on Mac.)
613  dc.DestroyClippingRegion();
614  DrawOverlays(true, &dc);
615  }
616 
617 #if DEBUG_DRAW_TIMING
618  sw.Pause();
619  wxLogDebug(wxT("Total: %ld milliseconds"), sw.Time());
620  wxPrintf(wxT("Total: %ld milliseconds\n"), sw.Time());
621 #endif
622 }
623 
624 void TrackPanel::MakeParentModifyState(bool bWantsAutoSave)
625 {
626  mListener->TP_ModifyState(bWantsAutoSave);
627 }
628 
630 {
632 }
633 
635 {
636  if (mUIHandle && mUIHandle->StopsOnKeystroke() ) {
637  // The bogus id isn't used anywhere, but may help with debugging.
638  // as this is sending a bogus mouse up. The mouse button is still actually down
639  // and may go up again.
640  const int idBogusUp = 2;
641  wxMouseEvent evt { wxEVT_LEFT_UP };
642  evt.SetId( idBogusUp );
643  evt.SetPosition(this->ScreenToClient(::wxGetMousePosition()));
644  this->ProcessEvent(evt);
645  }
646 }
647 
648 namespace
649 {
650  void ProcessUIHandleResult
651  (TrackPanel *panel, AdornedRulerPanel *ruler,
652  Track *pClickedTrack, Track *pLatestTrack,
653  UIHandle::Result refreshResult)
654  {
655  // TODO: make a finer distinction between refreshing the track control area,
656  // and the waveform area. As it is, redraw both whenever you must redraw either.
657 
658  using namespace RefreshCode;
659 
660  if (refreshResult & DestroyedCell) {
661  panel->UpdateViewIfNoTracks();
662  // Beware stale pointer!
663  if (pLatestTrack == pClickedTrack)
664  pLatestTrack = NULL;
665  pClickedTrack = NULL;
666  }
667 
668  if (pClickedTrack && (refreshResult & UpdateVRuler))
669  panel->UpdateVRuler(pClickedTrack);
670 
671  if (refreshResult & DrawOverlays) {
672  panel->DrawOverlays(false);
673  ruler->DrawOverlays(false);
674  }
675 
676  // Refresh all if told to do so, or if told to refresh a track that
677  // is not known.
678  const bool refreshAll =
679  ( (refreshResult & RefreshAll)
680  || ((refreshResult & RefreshCell) && !pClickedTrack)
681  || ((refreshResult & RefreshLatestCell) && !pLatestTrack));
682 
683  if (refreshAll)
684  panel->Refresh(false);
685  else {
686  if (refreshResult & RefreshCell)
687  panel->RefreshTrack(pClickedTrack);
688  if (refreshResult & RefreshLatestCell)
689  panel->RefreshTrack(pLatestTrack);
690  }
691 
692  if (refreshResult & FixScrollbars)
694 
695  if (refreshResult & Resize)
696  panel->GetListener()->TP_HandleResize();
697 
698  // This flag is superfluous if you do full refresh,
699  // because TrackPanel::Refresh() does this too
700  if (refreshResult & UpdateSelection) {
701  panel->DisplaySelection();
702 
703  {
704  // Formerly in TrackPanel::UpdateSelectionDisplay():
705 
706  // Make sure the ruler follows suit.
707  // mRuler->DrawSelection();
708 
709  // ... but that too is superfluous it does nothing but refresh
710  // the ruler, while DisplaySelection calls TP_DisplaySelection which
711  // also always refreshes the ruler.
712  }
713  }
714 
715  if ((refreshResult & EnsureVisible) && pClickedTrack)
716  panel->EnsureVisible(pClickedTrack);
717  }
718 }
719 
720 void TrackPanel::Uncapture(wxMouseState *pState)
721 {
722  auto state = ::wxGetMouseState();
723  if (!pState) {
724  // Remap the position
725  state.SetPosition(this->ScreenToClient(state.GetPosition()));
726  pState = &state;
727  }
728 
729  if (HasCapture())
730  ReleaseMouse();
731  HandleMotion( *pState );
732 }
733 
735 {
736  if (mUIHandle) {
737  // copy shared_ptr for safety, as in HandleClick
738  auto handle = mUIHandle;
739  // UIHANDLE CANCEL
740  UIHandle::Result refreshResult = handle->Cancel(GetProject());
741  auto pTrack = GetTracks()->Lock(mpClickedTrack);
742  if (pTrack)
743  ProcessUIHandleResult(
744  this, mRuler, pTrack.get(), NULL,
745  refreshResult | mMouseOverUpdateFlags );
746  mpClickedTrack.reset();
747  mUIHandle.reset(), handle.reset(), ClearTargets();
748  Uncapture();
749  return true;
750  }
751  return false;
752 }
753 
755 {
756  if (!down)
757  return false;
758 
759  {
760  auto target = Target();
761  if (target && target->HasEscape() && target->Escape()) {
763  return true;
764  }
765  }
766 
767  if (mUIHandle) {
768  CancelDragging();
769  return true;
770  }
771 
772  if (ChangeTarget(true, false)) {
774  return true;
775  }
776 
777  return false;
778 }
779 
780 void TrackPanel::UpdateMouseState(const wxMouseState &state)
781 {
782  mLastMouseState = state;
783 
784  // Simulate a down button if none, so hit test routines can anticipate
785  // which button will be clicked
786  if (!state.ButtonIsDown(wxMOUSE_BTN_ANY)) {
787 #ifdef __WXOSX__
788  if (state.RawControlDown())
789  // On Mac we can distinctly anticipate "right" click (as Control+click)
790  mLastMouseState.SetRightDown( true ),
791  mLastMouseState.SetLeftDown( false );
792  else
793 #endif
794  // Anticipate a left click by default
795  mLastMouseState.SetRightDown( false ),
796  mLastMouseState.SetLeftDown( true );
797  }
798 }
799 
801 {
803 }
804 
806 {
808 }
809 
811 {
813 }
814 
816 {
817  // Come here on modifier key or mouse button transitions,
818  // or on starting or stopping of play or record,
819  // or change of toolbar button,
820  // and change the cursor appropriately.
821 
822  // Get the button and key states
823  auto state = ::wxGetMouseState();
824  // Remap the position
825  state.SetPosition(this->ScreenToClient(state.GetPosition()));
826 
827  HandleMotion( state, doHit );
828 }
829 
831 {
833  return p->IsAudioActive();
834 }
835 
836 
844 void TrackPanel::HandleMotion( wxMouseState &inState, bool doHit )
845 {
846  UpdateMouseState( inState );
847 
848  const auto foundCell = FindCell( inState.m_x, inState.m_y );
849  auto &rect = foundCell.rect;
850  auto &pCell = foundCell.pCell;
851  const TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell };
852  HandleMotion( tpmState, doHit );
853 }
854 
856 ( const TrackPanelMouseState &tpmState, bool doHit )
857 {
858  auto handle = mUIHandle;
859 
860  auto newCell = tpmState.pCell;
861 
862  std::shared_ptr<Track> newTrack;
863  if ( newCell )
864  newTrack = static_cast<CommonTrackPanelCell*>( newCell.get() )->
865  FindTrack();
866 
867  wxString status{}, tooltip{};
868  wxCursor *pCursor{};
869  unsigned refreshCode = 0;
870 
871  if ( ! doHit ) {
872  // Dragging or not
873  handle = Target();
874 
875  // Assume cell does not change but target does
876  refreshCode = mMouseOverUpdateFlags;
877  mMouseOverUpdateFlags = 0;
878  }
879  else if ( !mUIHandle ) {
880  // Not yet dragging.
881 
882  auto oldCell = mLastCell.lock();
883  std::shared_ptr<Track> oldTrack;
884  if ( oldCell )
885  oldTrack = static_cast<CommonTrackPanelCell*>( oldCell.get() )->
886  FindTrack();
887 
888  unsigned updateFlags = mMouseOverUpdateFlags;
889 
890  // First check whether crossing cell to cell
891  if ( newCell == oldCell )
892  oldCell.reset();
893  else {
894  // Forget old targets
895  ClearTargets();
896  // Re-draw any highlighting
897  if (oldCell) {
898  ProcessUIHandleResult(
899  this, GetRuler(), oldTrack.get(), oldTrack.get(), updateFlags);
900  }
901  }
902 
903  auto oldHandle = Target();
904  auto oldPosition = mTarget;
905 
906  // Now do the
907  // UIHANDLE HIT TEST !
908  mTargets = newCell->HitTest(tpmState, GetProject());
909 
910  mTarget = 0;
911 
912  // Find the old target's NEW place if we can
913  if (oldHandle) {
914  auto begin = mTargets.begin(), end = mTargets.end(),
915  iter = std::find(begin, end, oldHandle);
916  if (iter != end) {
917  size_t newPosition = iter - begin;
918  if (newPosition <= oldPosition)
919  mTarget = newPosition;
920  // else, some NEW hit and this position takes priority
921  }
922  }
923 
924  handle = Target();
925 
926  mLastCell = newCell;
927 
928  if (!oldCell && oldHandle != handle)
929  // Did not move cell to cell, but did change the target
930  refreshCode = updateFlags;
931 
932  if (handle && handle != oldHandle)
933  handle->Enter(true);
934  }
935 
936  // UIHANDLE PREVIEW
937  // Update status message and cursor, whether dragging or not
938  if (handle) {
939  auto preview = handle->Preview( tpmState, GetProject() );
940  status = preview.message;
941  tooltip = preview.tooltip;
942  pCursor = preview.cursor;
943  auto code = handle->GetChangeHighlight();
944  handle->SetChangeHighlight(RefreshCode::RefreshNone);
945  refreshCode |= code;
946  mMouseOverUpdateFlags |= code;
947  }
948  if (!pCursor) {
949  static wxCursor defaultCursor{ wxCURSOR_ARROW };
950  pCursor = &defaultCursor;
951  }
952 
953  if (HasEscape())
954  /* i18n-hint Esc is a key on the keyboard */
955  status += wxT(" "), status += _("(Esc to cancel)");
956  mListener->TP_DisplayStatusMessage(status);
957  if (tooltip != GetToolTipText()) {
958  // Unset first, by analogy with AButton
959  UnsetToolTip();
960  SetToolTip(tooltip);
961  }
962  if (pCursor)
963  SetCursor( *pCursor );
964 
965  ProcessUIHandleResult(
966  this, GetRuler(), newTrack.get(), newTrack.get(), refreshCode);
967 }
968 
970 {
971  // Is there a nontrivial TAB key rotation?
972  if ( mTargets.size() > 1 )
973  return true;
974  auto target = Target();
975  return target && target->HasRotation();
976 }
977 
979 {
980  if (IsMouseCaptured())
981  return true;
982 
983  if (mTarget + 1 == mTargets.size() &&
984  Target() &&
985  !Target()->HasEscape())
986  return false;
987 
988  return mTargets.size() > 0;
989 }
990 
991 bool TrackPanel::ChangeTarget(bool forward, bool cycle)
992 {
993  auto size = mTargets.size();
994 
995  auto target = Target();
996  if (target && target->HasRotation()) {
997  if(target->Rotate(forward))
998  return true;
999  else if (cycle && (size == 1 || IsMouseCaptured())) {
1000  // Rotate through the states of this target only.
1001  target->Enter(forward);
1002  return true;
1003  }
1004  }
1005 
1006  if (!cycle &&
1007  ((forward && mTarget + 1 == size) ||
1008  (!forward && mTarget == 0)))
1009  return false;
1010 
1011  if (size > 1) {
1012  if (forward)
1013  ++mTarget;
1014  else
1015  mTarget += size - 1;
1016  mTarget %= size;
1017  if (Target())
1018  Target()->Enter(forward);
1019  return true;
1020  }
1021 
1022  return false;
1023 }
1024 
1026 {
1027  // Full refresh since the label area may need to indicate
1028  // newly selected tracks.
1029  Refresh(false);
1030 
1031  // Make sure the ruler follows suit.
1032  mRuler->DrawSelection();
1033 
1034  // As well as the SelectionBar.
1035  DisplaySelection();
1036 }
1037 
1039 {
1040  if (mAx)
1041  mAx->Updated();
1042 }
1043 
1044 // Counts tracks, counting stereo tracks as one track.
1046 {
1047  size_t count = 0;
1049  for (auto t = iter.First(); t; t = iter.Next()) {
1050  count += 1;
1051  if( t->GetLinked() ){
1052  t = iter.Next();
1053  if( !t )
1054  break;
1055  }
1056  }
1057  return count;
1058 }
1059 
1060 // Counts selected tracks, counting stereo tracks as one track.
1062 {
1063  size_t count = 0;
1064 
1066  for (auto t = iter.First(); t; t = iter.Next()) {
1067  count += t->GetSelected() ? 1:0;
1068  if( t->GetLinked() ){
1069  t = iter.Next();
1070  if( !t )
1071  break;
1072  }
1073  }
1074  return count;
1075 }
1076 
1077 void TrackPanel::MessageForScreenReader(const wxString& message)
1078 {
1079  if (mAx)
1080  mAx->MessageForScreenReader(message);
1081 }
1082 
1085 {
1086  return mUIHandle != NULL;
1087 }
1088 
1090 {
1091  if (mTracks->empty())
1092  {
1093  // BG: There are no more tracks on screen
1094  //BG: Set zoom to normal
1096 
1097  //STM: Set selection to 0,0
1098  //PRL: and default the rest of the selection information
1100 
1101  // PRL: Following causes the time ruler to align 0 with left edge.
1102  // Bug 972
1103  mViewInfo->h = 0;
1104 
1107  mListener->TP_DisplayStatusMessage(wxT("")); //STM: Clear message if all tracks are removed
1108  }
1109 }
1110 
1111 void TrackPanel::OnPlayback(wxCommandEvent &e)
1112 {
1113  e.Skip();
1114  // Starting or stopping of play or record affects some cursors.
1115  // Start or stop is in progress now, not completed; so delay the cursor
1116  // change until next idle time.
1117  CallAfter( [this] { HandleCursorForPresentMouseState(); } );
1118 }
1119 
1120 // The tracks positions within the list have changed, so update the vertical
1121 // ruler size for the track that triggered the event.
1122 void TrackPanel::OnTrackListResizing(wxCommandEvent & e)
1123 {
1124  auto t = static_cast<TrackListEvent&>(e).mpTrack.lock();
1125  // A deleted track can trigger the event. In which case do nothing here.
1126  if( t )
1127  UpdateVRuler(t.get());
1128  e.Skip();
1129 }
1130 
1131 // Tracks have been removed from the list.
1132 void TrackPanel::OnTrackListDeletion(wxCommandEvent & e)
1133 {
1134  if (mUIHandle) {
1135  // copy shared_ptr for safety, as in HandleClick
1136  auto handle = mUIHandle;
1137  handle->OnProjectChange(GetProject());
1138  }
1139 
1140  // If the focused track disappeared but there are still other tracks,
1141  // this reassigns focus.
1142  GetFocusedTrack();
1143 
1144  UpdateVRulerSize();
1145 
1146  e.Skip();
1147 }
1148 
1149 void TrackPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
1150 {
1151  OnTrackMenu();
1152 }
1153 
1155  using DrawFunction = void (*)(
1156  TrackPanelDrawingContext &context,
1157  const wxRect &rect,
1158  const Track *maybeNULL
1159  );
1160 
1161  unsigned items; // a bitwise OR of values of the enum above
1162  int height;
1165 };
1166 
1167 namespace {
1168 
1169 #define RANGE(array) (array), (array) + sizeof(array)/sizeof(*(array))
1170 using TCPLines = std::vector< TrackInfo::TCPLine >;
1171 
1172 enum : unsigned {
1173  // The sequence is not significant, just keep bits distinct
1174  kItemBarButtons = 1 << 0,
1175  kItemStatusInfo1 = 1 << 1,
1176  kItemMute = 1 << 2,
1177  kItemSolo = 1 << 3,
1178  kItemGain = 1 << 4,
1179  kItemPan = 1 << 5,
1180  kItemVelocity = 1 << 6,
1181  kItemMidiControlsRect = 1 << 7,
1182  kItemMinimize = 1 << 8,
1183  kItemSyncLock = 1 << 9,
1184  kItemStatusInfo2 = 1 << 10,
1185 
1186  kHighestBottomItem = kItemMinimize,
1187 };
1188 
1189 
1190 #ifdef EXPERIMENTAL_DA
1191 
1192  #define TITLE_ITEMS \
1193  { kItemBarButtons, kTrackInfoBtnSize, 4, \
1194  &TrackInfo::CloseTitleDrawFunction },
1195  // DA: Has Mute and Solo on separate lines.
1196  #define MUTE_SOLO_ITEMS(extra) \
1197  { kItemMute, kTrackInfoBtnSize + 1, 1, \
1198  &TrackInfo::WideMuteDrawFunction }, \
1199  { kItemSolo, kTrackInfoBtnSize + 1, extra, \
1200  &TrackInfo::WideSoloDrawFunction },
1201  // DA: Does not have status information for a track.
1202  #define STATUS_ITEMS
1203 
1204 #else
1205 
1206  #define TITLE_ITEMS \
1207  { kItemBarButtons, kTrackInfoBtnSize, 0, \
1208  &TrackInfo::CloseTitleDrawFunction },
1209  #define MUTE_SOLO_ITEMS(extra) \
1210  { kItemMute | kItemSolo, kTrackInfoBtnSize + 1, extra, \
1211  &TrackInfo::MuteAndSoloDrawFunction },
1212  #define STATUS_ITEMS \
1213  { kItemStatusInfo1, 12, 0, \
1214  &TrackInfo::Status1DrawFunction }, \
1215  { kItemStatusInfo2, 12, 0, \
1216  &TrackInfo::Status2DrawFunction },
1217 
1218 #endif
1219 
1220 #define COMMON_ITEMS \
1221  TITLE_ITEMS
1222 
1223 const TrackInfo::TCPLine defaultCommonTrackTCPLines[] = {
1224  COMMON_ITEMS
1225 };
1226 TCPLines commonTrackTCPLines{ RANGE(defaultCommonTrackTCPLines) };
1227 
1228 const TrackInfo::TCPLine defaultWaveTrackTCPLines[] = {
1229  COMMON_ITEMS
1230  MUTE_SOLO_ITEMS(2)
1235  STATUS_ITEMS
1236 };
1237 TCPLines waveTrackTCPLines{ RANGE(defaultWaveTrackTCPLines) };
1238 
1239 const TrackInfo::TCPLine defaultNoteTrackTCPLines[] = {
1240  COMMON_ITEMS
1241 #ifdef EXPERIMENTAL_MIDI_OUT
1242  MUTE_SOLO_ITEMS(0)
1243  { kItemMidiControlsRect, kMidiCellHeight * 4, 0,
1246  &TrackInfo::VelocitySliderDrawFunction },
1247 #endif
1248 };
1249 TCPLines noteTrackTCPLines{ RANGE(defaultNoteTrackTCPLines) };
1250 
1251 int totalTCPLines( const TCPLines &lines, bool omitLastExtra )
1252 {
1253  int total = 0;
1254  int lastExtra = 0;
1255  for ( const auto line : lines ) {
1256  lastExtra = line.extraSpace;
1257  total += line.height + lastExtra;
1258  }
1259  if (omitLastExtra)
1260  total -= lastExtra;
1261  return total;
1262 }
1263 
1264 const TCPLines &getTCPLines( const Track &track )
1265 {
1266 #ifdef USE_MIDI
1267  if ( track.GetKind() == Track::Note )
1268  return noteTrackTCPLines;
1269 #endif
1270 
1271  if ( track.GetKind() == Track::Wave )
1272  return waveTrackTCPLines;
1273 
1274  return commonTrackTCPLines;
1275 }
1276 
1277 // return y value and height
1278 std::pair< int, int > CalcItemY( const TCPLines &lines, unsigned iItem )
1279 {
1280  int y = 0;
1281  auto pLines = lines.begin();
1282  while ( pLines != lines.end() &&
1283  0 == (pLines->items & iItem) ) {
1284  y += pLines->height + pLines->extraSpace;
1285  ++pLines;
1286  }
1287  int height = 0;
1288  if ( pLines != lines.end() )
1289  height = pLines->height;
1290  return { y, height };
1291 }
1292 
1293 // Items for the bottom of the panel, listed bottom-upwards
1294 // As also with the top items, the extra space is below the item
1295 const TrackInfo::TCPLine defaultCommonTrackTCPBottomLines[] = {
1296  // The '0' avoids impinging on bottom line of TCP
1297  // Use -1 if you do want to do so.
1298  { kItemSyncLock | kItemMinimize, kTrackInfoBtnSize, 0,
1300 };
1301 TCPLines commonTrackTCPBottomLines{ RANGE(defaultCommonTrackTCPBottomLines) };
1302 
1303 // return y value and height
1304 std::pair< int, int > CalcBottomItemY
1305  ( const TCPLines &lines, unsigned iItem, int height )
1306 {
1307  int y = height;
1308  auto pLines = lines.begin();
1309  while ( pLines != lines.end() &&
1310  0 == (pLines->items & iItem) ) {
1311  y -= pLines->height + pLines->extraSpace;
1312  ++pLines;
1313  }
1314  if (pLines != lines.end())
1315  y -= (pLines->height + pLines->extraSpace );
1316  return { y, pLines->height };
1317 }
1318 
1319 }
1320 
1322 {
1323  unsigned height = 0;
1324  if (!commonTrackTCPLines.empty())
1325  height += commonTrackTCPLines.front().height;
1326  if (!commonTrackTCPBottomLines.empty())
1327  height += commonTrackTCPBottomLines.front().height;
1328  // + 1 prevents the top item from disappearing for want of enough space,
1329  // according to the rules in HideTopItem.
1330  return height + kTopMargin + kBottomMargin + 1;
1331 }
1332 
1333 bool TrackInfo::HideTopItem( const wxRect &rect, const wxRect &subRect,
1334  int allowance ) {
1335  auto limit = CalcBottomItemY
1336  ( commonTrackTCPBottomLines, kHighestBottomItem, rect.height).first;
1337  // Return true if the rectangle is even touching the limit
1338  // without an overlap. That was the behavior as of 2.1.3.
1339  return subRect.y + subRect.height - allowance >= rect.y + limit;
1340 }
1341 
1344 {
1345  auto pCell = tpmEvent.pCell;
1346  if (!pCell)
1347  return;
1348 
1349  auto &event = tpmEvent.event;
1350  double steps {};
1351 #if defined(__WXMAC__) && defined(EVT_MAGNIFY)
1352  // PRL:
1353  // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from
1354  // the future in custom build of 3.0.2
1355  if (event.Magnify()) {
1356  event.SetControlDown(true);
1357  steps = 2 * event.GetMagnification();
1358  }
1359  else
1360 #endif
1361  {
1362  steps = event.m_wheelRotation /
1363  (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
1364  }
1365 
1366  if(event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL) {
1367  // Two-fingered horizontal swipe on mac is treated like shift-mousewheel
1368  event.SetShiftDown(true);
1369  // This makes the wave move in the same direction as the fingers, and the scrollbar
1370  // thumb moves oppositely
1371  steps *= -1;
1372  }
1373 
1374  tpmEvent.steps = steps;
1375 
1376  if(!event.HasAnyModifiers()) {
1377  // We will later un-skip if we do anything, but if we don't,
1378  // propagate the event up for the sake of the scrubber
1379  event.Skip();
1380  event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
1381  }
1382 
1383  unsigned result =
1384  pCell->HandleWheelRotation( tpmEvent, GetProject() );
1385  auto pTrack = static_cast<CommonTrackPanelCell*>(pCell.get())->FindTrack();
1386  ProcessUIHandleResult(
1387  this, mRuler, pTrack.get(), pTrack.get(), result);
1388 }
1389 
1390 void TrackPanel::OnCaptureKey(wxCommandEvent & event)
1391 {
1392  mEnableTab = false;
1393  wxKeyEvent *kevent = static_cast<wxKeyEvent *>(event.GetEventObject());
1394  const auto code = kevent->GetKeyCode();
1395  if ( WXK_ESCAPE != code )
1397 
1398  // TODO? Some notion of focused cell, more generally than focused tracks
1399 
1400  // Give focused track precedence
1401  Track * const t = GetFocusedTrack();
1402  if (t) {
1403  const unsigned refreshResult =
1404  ((TrackPanelCell*)t)->CaptureKey(*kevent, *mViewInfo, this);
1405  ProcessUIHandleResult(this, mRuler, t, t, refreshResult);
1406  event.Skip(kevent->GetSkipped());
1407  }
1408 
1409 #if 0
1410  // Special TAB key handling, but only if the track didn't capture it
1411  if ( !(t && !kevent->GetSkipped()) &&
1412  WXK_TAB == code && HasRotation() ) {
1413  // Override TAB navigation in wxWidgets, by not skipping
1414  event.Skip(false);
1415  mEnableTab = true;
1416  return;
1417  }
1418  else
1419 #endif
1420  if (!t)
1421  event.Skip();
1422 }
1423 
1424 void TrackPanel::OnKeyDown(wxKeyEvent & event)
1425 {
1426  switch (event.GetKeyCode())
1427  {
1428  case WXK_ESCAPE:
1429  if(HandleEscapeKey(true))
1430  // Don't skip the event, eat it so that
1431  // AudacityApp does not also stop any playback.
1432  return;
1433  else
1434  break;
1435 
1436  case WXK_ALT:
1437  case WXK_SHIFT:
1438  case WXK_CONTROL:
1439 #ifdef __WXOSX__
1440  case WXK_RAW_CONTROL:
1441 #endif
1443  break;
1444 
1445  // Allow PageUp and PageDown keys to
1446  //scroll the Track Panel left and right
1447  case WXK_PAGEUP:
1448  HandlePageUpKey();
1449  return;
1450 
1451  case WXK_PAGEDOWN:
1453  return;
1454 
1455 #if 0
1456  case WXK_TAB:
1457  if ( mEnableTab && HasRotation() ) {
1458  ChangeTarget( !event.ShiftDown(), true );
1460  return;
1461  }
1462  else
1463  break;
1464 #endif
1465  }
1466 
1467  Track *const t = GetFocusedTrack();
1468 
1469  if (t) {
1470  const unsigned refreshResult =
1471  ((TrackPanelCell*)t)->KeyDown(event, *mViewInfo, this);
1472  ProcessUIHandleResult(this, mRuler, t, t, refreshResult);
1473  }
1474  else
1475  event.Skip();
1476 }
1477 
1478 void TrackPanel::OnChar(wxKeyEvent & event)
1479 {
1480  switch (event.GetKeyCode())
1481  {
1482  case WXK_ESCAPE:
1483  case WXK_ALT:
1484  case WXK_SHIFT:
1485  case WXK_CONTROL:
1486  case WXK_PAGEUP:
1487  case WXK_PAGEDOWN:
1488  return;
1489  }
1490 
1491  Track *const t = GetFocusedTrack();
1492  if (t) {
1493  const unsigned refreshResult =
1494  ((TrackPanelCell*)t)->Char(event, *mViewInfo, this);
1495  ProcessUIHandleResult(this, mRuler, t, t, refreshResult);
1496  }
1497  else
1498  event.Skip();
1499 }
1500 
1501 void TrackPanel::OnKeyUp(wxKeyEvent & event)
1502 {
1503  bool didSomething = false;
1504  switch (event.GetKeyCode())
1505  {
1506  case WXK_ESCAPE:
1507  didSomething = HandleEscapeKey(false);
1508  break;
1509 
1510  case WXK_ALT:
1511  case WXK_SHIFT:
1512  case WXK_CONTROL:
1513 #ifdef __WXOSX__
1514  case WXK_RAW_CONTROL:
1515 #endif
1517  break;
1518  }
1519 
1520  if (didSomething)
1521  return;
1522 
1523  Track * const t = GetFocusedTrack();
1524  if (t) {
1525  const unsigned refreshResult =
1526  ((TrackPanelCell*)t)->KeyUp(event, *mViewInfo, this);
1527  ProcessUIHandleResult(this, mRuler, t, t, refreshResult);
1528  return;
1529  }
1530 
1531  event.Skip();
1532 }
1533 
1535 void TrackPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
1536 {
1537  ClearTargets();
1538 
1539  // This is bad. We are lying abou the event by saying it is a mouse up.
1540  wxMouseEvent e(wxEVT_LEFT_UP);
1541  e.SetId( kCaptureLostEventId );
1542 
1543  e.m_x = mMouseMostRecentX;
1544  e.m_y = mMouseMostRecentY;
1545 
1546  OnMouseEvent(e);
1547 }
1548 
1552 void TrackPanel::OnMouseEvent(wxMouseEvent & event)
1553 try
1554 {
1555  const auto foundCell = FindCell( event.m_x, event.m_y );
1556  auto &rect = foundCell.rect;
1557  auto &pCell = foundCell.pCell;
1558  auto &pTrack = foundCell.pTrack;
1559 
1560  const auto size = GetSize();
1561  TrackPanelMouseEvent tpmEvent{ event, rect, size, pCell };
1562 
1563 #if defined(__WXMAC__) && defined(EVT_MAGNIFY)
1564  // PRL:
1565  // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from
1566  // the future in custom build of 3.0.2
1567  if (event.Magnify()) {
1568  HandleWheelRotation( tpmEvent );
1569  }
1570 #endif
1571 
1572  // If a mouse event originates from a keyboard context menu event then
1573  // event.GetPosition() == wxDefaultPosition. wxContextMenu events are handled in
1574  // TrackPanel::OnContextMenu(), and therefore associated mouse events are ignored here.
1575  // Not ignoring them was causing bug 613: the mouse events were interpreted as clicking
1576  // outside the tracks.
1577  if (event.GetPosition() == wxDefaultPosition && (event.RightDown() || event.RightUp())) {
1578  event.Skip();
1579  return;
1580  }
1581 
1582  if (event.m_wheelRotation != 0)
1583  HandleWheelRotation( tpmEvent );
1584 
1585  if (event.LeftDown() || event.LeftIsDown() || event.Moving()) {
1586  // Skip, even if we do something, so that the left click or drag
1587  // may have an additional effect in the scrubber.
1588  event.Skip();
1589  event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
1590  }
1591 
1592  mMouseMostRecentX = event.m_x;
1593  mMouseMostRecentY = event.m_y;
1594 
1595  if (event.LeftDown()) {
1596  // The activate event is used to make the
1597  // parent window 'come alive' if it didn't have focus.
1598  wxActivateEvent e;
1599  GetParent()->GetEventHandler()->ProcessEvent(e);
1600 
1601  // wxTimers seem to be a little unreliable, so this
1602  // "primes" it to make sure it keeps going for a while...
1603 
1604  // When this timer fires, we call TrackPanel::OnTimer and
1605  // possibly update the screen for offscreen scrolling.
1606  mTimer.Stop();
1607  mTimer.Start(kTimerInterval, FALSE);
1608  }
1609 
1610  if (event.ButtonDown()) {
1611  SetFocus();
1612  }
1613 
1614  if (event.Leaving())
1615  {
1616  if ( !mUIHandle )
1617  ClearTargets();
1618 
1619  auto buttons =
1620  // Bug 1325: button state in Leaving events is unreliable on Mac.
1621  // Poll the global state instead.
1622  // event.ButtonIsDown(wxMOUSE_BTN_ANY);
1623  ::wxGetMouseState().ButtonIsDown(wxMOUSE_BTN_ANY);
1624 
1625  if(!buttons) {
1626  CancelDragging();
1627 
1628 #if defined(__WXMAC__)
1629 
1630  // We must install the cursor ourselves since the window under
1631  // the mouse is no longer this one and wx2.8.12 makes that check.
1632  // Should re-evaluate with wx3.
1633  wxSTANDARD_CURSOR->MacInstall();
1634 #endif
1635  }
1636  }
1637 
1638  if (mUIHandle) {
1639  auto pClickedTrack = GetTracks()->Lock(mpClickedTrack);
1640  if (event.Dragging()) {
1641  // UIHANDLE DRAG
1642  // copy shared_ptr for safety, as in HandleClick
1643  auto handle = mUIHandle;
1644  const UIHandle::Result refreshResult =
1645  handle->Drag( tpmEvent, GetProject() );
1646  ProcessUIHandleResult
1647  (this, mRuler, pClickedTrack.get(), pTrack.get(), refreshResult);
1648  mMouseOverUpdateFlags |= refreshResult;
1649  if (refreshResult & RefreshCode::Cancelled) {
1650  // Drag decided to abort itself
1651  mUIHandle.reset(), handle.reset(), ClearTargets();
1652  mpClickedTrack.reset();
1653  Uncapture( &event );
1654  }
1655  else {
1656  UpdateMouseState(event);
1657  TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell };
1658  HandleMotion( tpmState );
1659  }
1660  }
1661  else if (event.ButtonUp()) {
1662  // UIHANDLE RELEASE
1663  unsigned moreFlags = mMouseOverUpdateFlags;
1664  UIHandle::Result refreshResult =
1665  mUIHandle->Release( tpmEvent, GetProject(), this );
1666  ProcessUIHandleResult
1667  (this, mRuler, pClickedTrack.get(), pTrack.get(),
1668  refreshResult | moreFlags);
1669  mUIHandle.reset(), ClearTargets();
1670  mpClickedTrack.reset();
1671  // will also Uncapture() below
1672  }
1673  }
1674  else if ( event.GetEventType() == wxEVT_MOTION )
1675  // Update status message and cursor, not during drag
1676  // consider it not a drag, even if button is down during motion, if
1677  // mUIHandle is null, as it becomes during interrupted drag
1678  // (e.g. by hitting space to play while dragging an envelope point)
1679  HandleMotion( event );
1680  else if ( event.ButtonDown() || event.ButtonDClick() )
1681  HandleClick( tpmEvent );
1682 
1683  if (event.ButtonDown() && IsMouseCaptured()) {
1684  if (!HasCapture())
1685  CaptureMouse();
1686  }
1687 
1688  //EnsureVisible should be called after the up-click.
1689  if (event.ButtonUp()) {
1690  Uncapture();
1691 
1692  wxRect rect;
1693 
1694  const auto foundCell = FindCell(event.m_x, event.m_y);
1695  auto t = foundCell.pTrack;
1696  if ( t )
1697  EnsureVisible(t.get());
1698  }
1699 }
1700 catch( ... )
1701 {
1702  // Abort any dragging, as if by hitting Esc
1703  if ( CancelDragging() )
1704  ;
1705  else {
1706  Uncapture();
1707  Refresh(false);
1708  }
1709  throw;
1710 }
1711 
1713 {
1714  auto pCell = tpmEvent.pCell;
1715  auto pTrack = static_cast<CommonTrackPanelCell *>( pCell.get() )->FindTrack();
1716 
1717  // Do hit test once more, in case the button really pressed was not the
1718  // one "anticipated."
1719  {
1720  TrackPanelMouseState tpmState{
1721  tpmEvent.event,
1722  tpmEvent.rect,
1723  tpmEvent.pCell
1724  };
1725  HandleMotion( tpmState );
1726  }
1727 
1728  mUIHandle = Target();
1729 
1730  if (mUIHandle) {
1731  // UIHANDLE CLICK
1732  // Make another shared pointer to the handle, in case recursive
1733  // event dispatching otherwise tries to delete the handle.
1734  auto handle = mUIHandle;
1735  UIHandle::Result refreshResult =
1736  handle->Click( tpmEvent, GetProject() );
1737  if (refreshResult & RefreshCode::Cancelled)
1738  mUIHandle.reset(), handle.reset(), ClearTargets();
1739  else {
1740  mpClickedTrack = pTrack;
1741 
1742  // Perhaps the clicked handle wants to update cursor and state message
1743  // after a click.
1744  TrackPanelMouseState tpmState{
1745  tpmEvent.event,
1746  tpmEvent.rect,
1747  tpmEvent.pCell
1748  };
1749  HandleMotion( tpmState );
1750  }
1751  ProcessUIHandleResult(
1752  this, mRuler, pTrack.get(), pTrack.get(), refreshResult);
1753  mMouseOverUpdateFlags |= refreshResult;
1754  }
1755 }
1756 
1758 {
1760 }
1761 
1762 void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
1763 {
1764  if (!trk)
1765  return;
1766 
1767  Track *link = trk->GetLink();
1768 
1769  if (link && !trk->GetLinked()) {
1770  trk = link;
1771  link = trk->GetLink();
1772  }
1773 
1774  // subtract insets and shadows from the rectangle, but not border
1775  // This matters because some separators do paint over the border
1776  wxRect rect(kLeftInset,
1777  -mViewInfo->vpos + trk->GetY() + kTopInset,
1778  GetRect().GetWidth() - kLeftInset - kRightInset - kShadowThickness,
1779  trk->GetHeight() - kTopInset - kShadowThickness);
1780 
1781  if (link) {
1782  rect.height += link->GetHeight();
1783  }
1784 
1785  if( refreshbacking )
1786  {
1787  mRefreshBacking = true;
1788  }
1789 
1790  Refresh( false, &rect );
1791 }
1792 
1793 
1798 void TrackPanel::Refresh(bool eraseBackground /* = TRUE */,
1799  const wxRect *rect /* = NULL */)
1800 {
1801  // Tell OnPaint() to refresh the backing bitmap.
1802  //
1803  // Originally I had the check within the OnPaint() routine and it
1804  // was working fine. That was until I found that, even though a full
1805  // refresh was requested, Windows only set the onscreen portion of a
1806  // window as damaged.
1807  //
1808  // So, if any part of the trackpanel was off the screen, full refreshes
1809  // didn't work and the display got corrupted.
1810  if( !rect || ( *rect == GetRect() ) )
1811  {
1812  mRefreshBacking = true;
1813  }
1814  wxWindow::Refresh(eraseBackground, rect);
1815  DisplaySelection();
1816 }
1817 
1818 #include "TrackPanelDrawingContext.h"
1819 
1823 void TrackPanel::DrawTracks(wxDC * dc)
1824 {
1825  wxRegion region = GetUpdateRegion();
1826 
1827  const wxRect clip = GetRect();
1828 
1829  wxRect panelRect = clip;
1830  panelRect.y = -mViewInfo->vpos;
1831 
1832  wxRect tracksRect = panelRect;
1833  tracksRect.x += GetLabelWidth();
1834  tracksRect.width -= GetLabelWidth();
1835 
1837  bool bMultiToolDown = pTtb->IsDown(multiTool);
1838  bool envelopeFlag = pTtb->IsDown(envelopeTool) || bMultiToolDown;
1839  bool bigPointsFlag = pTtb->IsDown(drawTool) || bMultiToolDown;
1840  bool sliderFlag = bMultiToolDown;
1841 
1842  TrackPanelDrawingContext context{ *dc, Target(), mLastMouseState };
1843 
1844  // The track artist actually draws the stuff inside each track
1845  auto first = GetProject()->GetFirstVisible();
1846  mTrackArtist->DrawTracks(context, GetTracks(), first.get(),
1847  region, tracksRect, clip,
1849  envelopeFlag, bigPointsFlag, sliderFlag);
1850 
1851  DrawEverythingElse(context, region, clip);
1852 }
1853 
1859  const wxRegion &region,
1860  const wxRect & clip)
1861 {
1862  // We draw everything else
1863  auto dc = &context.dc;
1864  wxRect focusRect(-1, -1, 0, 0);
1865  wxRect trackRect = clip;
1866  trackRect.height = 0; // for drawing background in no tracks case.
1867 
1869  for (Track *t = iter.First(); t; t = iter.Next()) {
1870  trackRect.y = t->GetY() - mViewInfo->vpos;
1871  trackRect.height = t->GetHeight();
1872 
1873  // If this track is linked to the next one, display a common
1874  // border for both, otherwise draw a normal border
1875  wxRect rect = trackRect;
1876  bool skipBorder = false;
1877  Track *l = t->GetLink();
1878 
1879  if (t->GetLinked()) {
1880  rect.height += l->GetHeight();
1881  }
1882  else if (l && trackRect.y >= 0) {
1883  skipBorder = true;
1884  }
1885 
1886  // If the previous track is linked to this one but isn't on the screen
1887  // (and thus would have been skipped by VisibleTrackIterator) we need to
1888  // draw that track's border instead.
1889  Track *borderTrack = t;
1890  wxRect borderRect = rect;
1891 
1892  if (l && !t->GetLinked() && trackRect.y < 0)
1893  {
1894  borderTrack = l;
1895 
1896  borderRect = trackRect;
1897  borderRect.y = l->GetY() - mViewInfo->vpos;
1898  borderRect.height = l->GetHeight();
1899 
1900  borderRect.height += t->GetHeight();
1901  }
1902 
1903  if (!skipBorder) {
1904  if (mAx->IsFocused(t)) {
1905  focusRect = borderRect;
1906  }
1907  DrawOutside(context, borderTrack, borderRect);
1908  }
1909 
1910  // Believe it or not, we can speed up redrawing if we don't
1911  // redraw the vertical ruler when only the waveform data has
1912  // changed. An example is during recording.
1913 
1914 #if DEBUG_DRAW_TIMING
1915 // wxRect rbox = region.GetBox();
1916 // wxPrintf(wxT("Update Region: %d %d %d %d\n"),
1917 // rbox.x, rbox.y, rbox.width, rbox.height);
1918 #endif
1919 
1920  if (region.Contains(0, trackRect.y, GetLeftOffset(), trackRect.height)) {
1921  wxRect rect = trackRect;
1922  rect.x += GetVRulerOffset();
1923  rect.y += kTopMargin;
1924  rect.width = GetVRulerWidth();
1925  rect.height -= (kTopMargin + kBottomMargin);
1926  mTrackArtist->DrawVRuler(context, t, rect);
1927  }
1928  }
1929 
1930  auto target = Target();
1931  if (target)
1932  target->DrawExtras(UIHandle::Cells, dc, region, clip);
1933 
1934  // Paint over the part below the tracks
1935  trackRect.y += trackRect.height;
1936  if (trackRect.y < clip.GetBottom()) {
1937  AColor::TrackPanelBackground(dc, false);
1938  dc->DrawRectangle(trackRect.x,
1939  trackRect.y,
1940  trackRect.width,
1941  clip.height - trackRect.y);
1942  }
1943 
1944  // Sometimes highlight is not drawn on backing bitmap. I thought
1945  // it was because FindFocus did not return "this" on Mac, but
1946  // when I removed that test, yielding this condition:
1947  // if (GetFocusedTrack() != NULL) {
1948  // the highlight was reportedly drawn even when something else
1949  // was the focus and no highlight should be drawn. -RBD
1950  if (GetFocusedTrack() != NULL && wxWindow::FindFocus() == this) {
1951  HighlightFocusedTrack(dc, focusRect);
1952  }
1953 
1954  if (target)
1955  target->DrawExtras(UIHandle::Panel, dc, region, clip);
1956 }
1957 
1958 // Make this #include go away!
1959 #include "tracks/ui/TrackControls.h"
1960 
1963  const wxRect &rect, const Track &track )
1964 {
1965  const auto topLines = getTCPLines( track );
1966  const auto bottomLines = commonTrackTCPBottomLines;
1967  DrawItems
1968  ( context, rect, &track, topLines, bottomLines );
1969 }
1970 
1973  const wxRect &rect, const Track *pTrack,
1974  const std::vector<TCPLine> &topLines, const std::vector<TCPLine> &bottomLines )
1975 {
1976  auto dc = &context.dc;
1978  dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
1979 
1980  {
1981  int yy = 0;
1982  for ( const auto &line : topLines ) {
1983  wxRect itemRect{
1984  rect.x, rect.y + yy,
1985  rect.width, line.height
1986  };
1987  if ( !TrackInfo::HideTopItem( rect, itemRect ) &&
1988  line.drawFunction )
1989  line.drawFunction( context, itemRect, pTrack );
1990  yy += line.height + line.extraSpace;
1991  }
1992  }
1993  {
1994  int yy = rect.height;
1995  for ( const auto &line : bottomLines ) {
1996  yy -= line.height + line.extraSpace;
1997  if ( line.drawFunction ) {
1998  wxRect itemRect{
1999  rect.x, rect.y + yy,
2000  rect.width, line.height
2001  };
2002  line.drawFunction( context, itemRect, pTrack );
2003  }
2004  }
2005  }
2006 }
2007 
2011  const wxRect &rect, const Track *pTrack )
2012 {
2013  auto dc = &context.dc;
2014  bool selected = pTrack ? pTrack->GetSelected() : true;
2015  {
2016  wxRect bev = rect;
2017  GetCloseBoxHorizontalBounds( rect, bev );
2018  auto target = dynamic_cast<CloseButtonHandle*>( context.target.get() );
2019  bool hit = target && target->GetTrack().get() == pTrack;
2020  bool captured = hit && target->IsClicked();
2021  bool down = captured && bev.Contains( context.lastState.GetPosition());
2022  AColor::Bevel2(*dc, !down, bev, selected, hit );
2023 
2024 #ifdef EXPERIMENTAL_THEMING
2025  wxPen pen( theTheme.Colour( clrTrackPanelText ));
2026  dc->SetPen( pen );
2027 #else
2028  dc->SetPen(*wxBLACK_PEN);
2029 #endif
2030  bev.Inflate( -1, -1 );
2031  // Draw the "X"
2032  const int s = 6;
2033 
2034  int ls = bev.x + ((bev.width - s) / 2);
2035  int ts = bev.y + ((bev.height - s) / 2);
2036  int rs = ls + s;
2037  int bs = ts + s;
2038 
2039  AColor::Line(*dc, ls, ts, rs, bs);
2040  AColor::Line(*dc, ls + 1, ts, rs + 1, bs);
2041  AColor::Line(*dc, rs, ts, ls, bs);
2042  AColor::Line(*dc, rs + 1, ts, ls + 1, bs);
2043 
2044  // bev.Inflate(-1, -1);
2045  }
2046 
2047  {
2048  wxRect bev = rect;
2049  GetTitleBarHorizontalBounds( rect, bev );
2050  auto target = dynamic_cast<MenuButtonHandle*>( context.target.get() );
2051  bool hit = target && target->GetTrack().get() == pTrack;
2052  bool captured = hit && target->IsClicked();
2053  bool down = captured && bev.Contains( context.lastState.GetPosition());
2054  wxString titleStr =
2055  pTrack ? pTrack->GetName() : _("Name");
2056 
2057  //bev.Inflate(-1, -1);
2058  AColor::Bevel2(*dc, !down, bev, selected, hit);
2059 
2060  // Draw title text
2061  SetTrackInfoFont(dc);
2062 
2063  // Bug 1660 The 'k' of 'Audio Track' was being truncated.
2064  // Constant of 32 found by counting pixels on a windows machine.
2065  // I believe it's the size of the X close button + the size of the
2066  // drop down arrow.
2067  int allowableWidth = rect.width - 32;
2068 
2069  wxCoord textWidth, textHeight;
2070  dc->GetTextExtent(titleStr, &textWidth, &textHeight);
2071  while (textWidth > allowableWidth) {
2072  titleStr = titleStr.Left(titleStr.Length() - 1);
2073  dc->GetTextExtent(titleStr, &textWidth, &textHeight);
2074  }
2075 
2076  // Pop-up triangle
2077  #ifdef EXPERIMENTAL_THEMING
2078  wxColour c = theTheme.Colour( clrTrackPanelText );
2079  #else
2080  wxColour c = *wxBLACK;
2081  #endif
2082 
2083  // wxGTK leaves little scraps (antialiasing?) of the
2084  // characters if they are repeatedly drawn. This
2085  // happens when holding down mouse button and moving
2086  // in and out of the title bar. So clear it first.
2087  // AColor::MediumTrackInfo(dc, t->GetSelected());
2088  // dc->DrawRectangle(bev);
2089 
2090  dc->SetTextForeground( c );
2091  dc->SetTextBackground( wxTRANSPARENT );
2092  dc->DrawText(titleStr, bev.x + 2, bev.y + (bev.height - textHeight) / 2);
2093 
2094 
2095 
2096  dc->SetPen(c);
2097  dc->SetBrush(c);
2098 
2099  int s = 10; // Width of dropdown arrow...height is half of width
2100  AColor::Arrow(*dc,
2101  bev.GetRight() - s - 3, // 3 to offset from right border
2102  bev.y + ((bev.height - (s / 2)) / 2),
2103  s);
2104 
2105  }
2106 }
2107 
2110  const wxRect &rect, const Track *pTrack )
2111 {
2112  auto dc = &context.dc;
2113  bool selected = pTrack ? pTrack->GetSelected() : true;
2114  bool syncLockSelected = pTrack ? pTrack->IsSyncLockSelected() : true;
2115  bool minimized = pTrack ? pTrack->GetMinimized() : false;
2116  {
2117  wxRect bev = rect;
2118  GetMinimizeHorizontalBounds(rect, bev);
2119  auto target = dynamic_cast<MinimizeButtonHandle*>( context.target.get() );
2120  bool hit = target && target->GetTrack().get() == pTrack;
2121  bool captured = hit && target->IsClicked();
2122  bool down = captured && bev.Contains( context.lastState.GetPosition());
2123 
2124  // Clear background to get rid of previous arrow
2125  //AColor::MediumTrackInfo(dc, t->GetSelected());
2126  //dc->DrawRectangle(bev);
2127 
2128  AColor::Bevel2(*dc, !down, bev, selected, hit);
2129 
2130 #ifdef EXPERIMENTAL_THEMING
2131  wxColour c = theTheme.Colour(clrTrackPanelText);
2132  dc->SetBrush(c);
2133  dc->SetPen(c);
2134 #else
2135  AColor::Dark(dc, selected);
2136 #endif
2137 
2138  AColor::Arrow(*dc,
2139  bev.x - 5 + bev.width / 2,
2140  bev.y - 2 + bev.height / 2,
2141  10,
2142  minimized);
2143  }
2144 
2145  // Draw the sync-lock indicator if this track is in a sync-lock selected group.
2146  if (syncLockSelected)
2147  {
2148  wxRect syncLockIconRect = rect;
2149 
2150  GetSyncLockHorizontalBounds( rect, syncLockIconRect );
2151  wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockIcon));
2152  // Icon is 12x12 and syncLockIconRect is 16x16.
2153  dc->DrawBitmap(syncLockBitmap,
2154  syncLockIconRect.x + 3,
2155  syncLockIconRect.y + 2,
2156  true);
2157  }
2158 }
2159 
2163  const wxRect &rect, const Track *pTrack )
2164 {
2165 #ifdef EXPERIMENTAL_MIDI_OUT
2166  auto target = dynamic_cast<NoteTrackButtonHandle*>( context.target.get() );
2167  bool hit = target && target->GetTrack().get() == pTrack;
2168  auto channel = hit ? target->GetChannel() : -1;
2169  auto &dc = context.dc;
2170  wxRect midiRect = rect;
2171  GetMidiControlsHorizontalBounds(rect, midiRect);
2172  NoteTrack::DrawLabelControls
2173  ( static_cast<const NoteTrack *>(pTrack), dc, midiRect, channel );
2174 #endif // EXPERIMENTAL_MIDI_OUT
2175 }
2176 
2177 template<typename TrackClass>
2179 ( LWSlider *(*Selector)
2180  (const wxRect &sliderRect, const TrackClass *t, bool captured, wxWindow*),
2181  wxDC *dc, const wxRect &rect, const Track *pTrack,
2182  bool captured, bool highlight )
2183 {
2184  wxRect sliderRect = rect;
2185  TrackInfo::GetSliderHorizontalBounds( rect.GetTopLeft(), sliderRect );
2186  auto wt = static_cast<const TrackClass*>( pTrack );
2187  Selector( sliderRect, wt, captured, nullptr )->OnPaint(*dc, highlight);
2188 }
2189 
2193  const wxRect &rect, const Track *pTrack )
2194 {
2195  auto target = dynamic_cast<PanSliderHandle*>( context.target.get() );
2196  auto dc = &context.dc;
2197  bool hit = target && target->GetTrack().get() == pTrack;
2198  bool captured = hit && target->IsClicked();
2199  SliderDrawFunction<WaveTrack>
2200  ( &TrackInfo::PanSlider, dc, rect, pTrack, captured, hit);
2201 }
2202 
2205  const wxRect &rect, const Track *pTrack )
2206 {
2207  auto target = dynamic_cast<GainSliderHandle*>( context.target.get() );
2208  auto dc = &context.dc;
2209  bool hit = target && target->GetTrack().get() == pTrack;
2210  bool captured = hit && target->IsClicked();
2211  SliderDrawFunction<WaveTrack>
2212  ( &TrackInfo::GainSlider, dc, rect, pTrack, captured, hit);
2213 }
2214 
2215 #ifdef EXPERIMENTAL_MIDI_OUT
2217 void TrackInfo::VelocitySliderDrawFunction
2218 ( TrackPanelDrawingContext &context,
2219  const wxRect &rect, const Track *pTrack )
2220 {
2221  auto dc = &context.dc;
2222  auto target = dynamic_cast<VelocitySliderHandle*>( context.target.get() );
2223  bool hit = target && target->GetTrack().get() == pTrack;
2224  bool captured = hit && target->IsClicked();
2225  SliderDrawFunction<NoteTrack>
2226  ( &TrackInfo::VelocitySlider, dc, rect, pTrack, captured, hit);
2227 }
2228 #endif
2229 
2231 ( wxDC *dc, const wxRect &bev, const Track *pTrack, bool down,
2232  bool WXUNUSED(captured),
2233  bool solo, bool hit )
2234 {
2235  //bev.Inflate(-1, -1);
2236  bool selected = pTrack ? pTrack->GetSelected() : true;
2237  auto pt = dynamic_cast<const PlayableTrack *>(pTrack);
2238  bool value = pt ? (solo ? pt->GetSolo() : pt->GetMute()) : false;
2239 
2240 #if 0
2241  AColor::MediumTrackInfo( dc, t->GetSelected());
2242  if( solo )
2243  {
2244  if( pt && pt->GetSolo() )
2245  {
2246  AColor::Solo(dc, pt->GetSolo(), t->GetSelected());
2247  }
2248  }
2249  else
2250  {
2251  if( pt && pt->GetMute() )
2252  {
2253  AColor::Mute(dc, pt->GetMute(), t->GetSelected(), pt->GetSolo());
2254  }
2255  }
2256  //(solo) ? AColor::Solo(dc, t->GetSolo(), t->GetSelected()) :
2257  // AColor::Mute(dc, t->GetMute(), t->GetSelected(), t->GetSolo());
2258  dc->SetPen( *wxTRANSPARENT_PEN );//No border!
2259  dc->DrawRectangle(bev);
2260 #endif
2261 
2262  wxCoord textWidth, textHeight;
2263  wxString str = (solo) ?
2264  /* i18n-hint: This is on a button that will silence all the other tracks.*/
2265  _("Solo") :
2266  /* i18n-hint: This is on a button that will silence this track.*/
2267  _("Mute");
2268 
2270  *dc,
2271  value == down,
2272  bev,
2273  selected, hit
2274  );
2275 
2276  SetTrackInfoFont(dc);
2277  dc->GetTextExtent(str, &textWidth, &textHeight);
2278  dc->DrawText(str, bev.x + (bev.width - textWidth) / 2, bev.y + (bev.height - textHeight) / 2);
2279 }
2280 
2284  const wxRect &rect, const Track *pTrack )
2285 {
2286  auto dc = &context.dc;
2287  wxRect bev = rect;
2288  GetWideMuteSoloHorizontalBounds( rect, bev );
2289  auto target = dynamic_cast<MuteButtonHandle*>( context.target.get() );
2290  bool hit = target && target->GetTrack().get() == pTrack;
2291  bool captured = hit && target->IsClicked();
2292  bool down = captured && bev.Contains( context.lastState.GetPosition());
2293  MuteOrSoloDrawFunction( dc, bev, pTrack, down, captured, false, hit );
2294 }
2295 
2298  const wxRect &rect, const Track *pTrack )
2299 {
2300  auto dc = &context.dc;
2301  wxRect bev = rect;
2302  GetWideMuteSoloHorizontalBounds( rect, bev );
2303  auto target = dynamic_cast<SoloButtonHandle*>( context.target.get() );
2304  bool hit = target && target->GetTrack().get() == pTrack;
2305  bool captured = hit && target->IsClicked();
2306  bool down = captured && bev.Contains( context.lastState.GetPosition());
2307  MuteOrSoloDrawFunction( dc, bev, pTrack, down, captured, true, hit );
2308 }
2309 
2312  const wxRect &rect, const Track *pTrack )
2313 {
2314  auto dc = &context.dc;
2315  bool bHasSoloButton = TrackPanel::HasSoloButton();
2316 
2317  wxRect bev = rect;
2318  if ( bHasSoloButton )
2319  GetNarrowMuteHorizontalBounds( rect, bev );
2320  else
2321  GetWideMuteSoloHorizontalBounds( rect, bev );
2322  {
2323  auto target = dynamic_cast<MuteButtonHandle*>( context.target.get() );
2324  bool hit = target && target->GetTrack().get() == pTrack;
2325  bool captured = hit && target->IsClicked();
2326  bool down = captured && bev.Contains( context.lastState.GetPosition());
2327  MuteOrSoloDrawFunction( dc, bev, pTrack, down, captured, false, hit );
2328  }
2329 
2330  if( !bHasSoloButton )
2331  return;
2332 
2333  GetNarrowSoloHorizontalBounds( rect, bev );
2334  {
2335  auto target = dynamic_cast<SoloButtonHandle*>( context.target.get() );
2336  bool hit = target && target->GetTrack().get() == pTrack;
2337  bool captured = hit && target->IsClicked();
2338  bool down = captured && bev.Contains( context.lastState.GetPosition());
2339  MuteOrSoloDrawFunction( dc, bev, pTrack, down, captured, true, hit );
2340  }
2341 }
2342 
2344  ( const wxString &string, wxDC *dc, const wxRect &rect )
2345 {
2346  static const int offset = 3;
2347  dc->DrawText(string, rect.x + offset, rect.y);
2348 }
2349 
2352  const wxRect &rect, const Track *pTrack )
2353 {
2354  auto dc = &context.dc;
2355  auto wt = static_cast<const WaveTrack*>(pTrack);
2356 
2360  auto rate = wt ? wt->GetRate() : 44100.0;
2361  wxString s;
2362  if (!wt || (wt->GetLinked()))
2363  s = _("Stereo, %dHz");
2364  else {
2365  if (wt->GetChannel() == Track::MonoChannel)
2366  s = _("Mono, %dHz");
2367  else if (wt->GetChannel() == Track::LeftChannel)
2368  s = _("Left, %dHz");
2369  else if (wt->GetChannel() == Track::RightChannel)
2370  s = _("Right, %dHz");
2371  }
2372  s = wxString::Format( s, (int) (rate + 0.5) );
2373 
2374  StatusDrawFunction( s, dc, rect );
2375 }
2376 
2379  const wxRect &rect, const Track *pTrack )
2380 {
2381  auto dc = &context.dc;
2382  auto wt = static_cast<const WaveTrack*>(pTrack);
2383  auto format = wt ? wt->GetSampleFormat() : floatSample;
2384  auto s = GetSampleFormatStr(format);
2385  StatusDrawFunction( s, dc, rect );
2386 }
2387 
2390  Track * t, const wxRect & rec)
2391 {
2392  auto dc = &context.dc;
2393  bool bIsWave = (t->GetKind() == Track::Wave);
2394 
2395  // Draw things that extend right of track control panel
2396  {
2397  // Start with whole track rect
2398  wxRect rect = rec;
2399  DrawOutsideOfTrack(context, t, rect);
2400 
2401  // Now exclude left, right, and top insets
2402  rect.x += kLeftInset;
2403  rect.y += kTopInset;
2404  rect.width -= kLeftInset * 2;
2405  rect.height -= kTopInset;
2406 
2407  int labelw = GetLabelWidth();
2408  int vrul = GetVRulerOffset();
2409  mTrackInfo.DrawBackground(dc, rect, t->GetSelected(), bIsWave, labelw, vrul);
2410 
2411  // Vaughan, 2010-08-24: No longer doing this.
2412  // Draw sync-lock tiles in ruler area.
2413  //if (t->IsSyncLockSelected()) {
2414  // wxRect tileFill = rect;
2415  // tileFill.x = GetVRulerOffset();
2416  // tileFill.width = GetVRulerWidth();
2417  // TrackArtist::DrawSyncLockTiles(dc, tileFill);
2418  //}
2419 
2420  DrawBordersAroundTrack(t, dc, rect, labelw, vrul);
2421  DrawShadow(t, dc, rect);
2422  }
2423 
2424  // Draw things within the track control panel
2425  wxRect rect = rec;
2426  rect.x += kLeftMargin;
2427  rect.width = kTrackInfoWidth - kLeftMargin;
2428  rect.y += kTopMargin;
2429  rect.height -= (kBottomMargin + kTopMargin);
2430 
2431  TrackInfo::DrawItems( context, rect, *t );
2432 
2433  //mTrackInfo.DrawBordersWithin( dc, rect, *t );
2434 }
2435 
2436 // Given rectangle should be the whole track rectangle
2437 // Paint the inset areas left, top, and right in a background color
2438 // If linked to a following channel, also paint the separator area, which
2439 // overlaps the next track rectangle's top
2441 (TrackPanelDrawingContext &context, Track * t, const wxRect & rect)
2442 {
2443  auto dc = &context.dc;
2444 
2445  // Fill in area outside of the track
2446  AColor::TrackPanelBackground(dc, false);
2447  wxRect side;
2448 
2449  // Area between panel border and left track border
2450  side = rect;
2451  side.width = kLeftInset;
2452  dc->DrawRectangle(side);
2453 
2454  // Area between panel border and top track border
2455  side = rect;
2456  side.height = kTopInset;
2457  dc->DrawRectangle(side);
2458 
2459  // Area between panel border and right track border
2460  side = rect;
2461  side.x += side.width - kTopInset;
2462  side.width = kTopInset;
2463  dc->DrawRectangle(side);
2464 
2465  // Area between tracks of stereo group
2466  if (t->GetLinked()) {
2467  // Paint the channel separator over (what would be) the shadow of the top
2468  // channel, and the top inset of the bottom channel
2469  side = rect;
2470  side.y += t->GetHeight() - kShadowThickness;
2471  side.height = kTopInset + kShadowThickness;
2472  dc->DrawRectangle(side);
2473  }
2474 }
2475 
2477 (const std::shared_ptr< TrackPanelCell > &pCell)
2478 {
2479  mpBackground = pCell;
2480 }
2481 
2482 std::shared_ptr< TrackPanelCell > TrackPanel::GetBackgroundCell()
2483 {
2484  return mpBackground;
2485 }
2486 
2488 void TrackPanel::HighlightFocusedTrack(wxDC * dc, const wxRect & rect)
2489 {
2490  wxRect theRect = rect;
2491  theRect.x += kLeftInset;
2492  theRect.y += kTopInset;
2493  theRect.width -= kLeftInset * 2;
2494  theRect.height -= kTopInset;
2495 
2496  dc->SetBrush(*wxTRANSPARENT_BRUSH);
2497 
2498  AColor::TrackFocusPen(dc, 0);
2499  dc->DrawRectangle(theRect.x - 1, theRect.y - 1, theRect.width + 2, theRect.height + 2);
2500 
2501  AColor::TrackFocusPen(dc, 1);
2502  dc->DrawRectangle(theRect.x - 2, theRect.y - 2, theRect.width + 4, theRect.height + 4);
2503 
2504  AColor::TrackFocusPen(dc, 2);
2505  dc->DrawRectangle(theRect.x - 3, theRect.y - 3, theRect.width + 6, theRect.height + 6);
2506 }
2507 
2509 {
2511  for (Track *t = iter.First(); t; t = iter.Next()) {
2512  UpdateTrackVRuler(t);
2513  }
2514 
2515  UpdateVRulerSize();
2516 }
2517 
2519 {
2520  if (t)
2521  UpdateTrackVRuler(t);
2522 
2523  UpdateVRulerSize();
2524 }
2525 
2527 {
2528  wxASSERT(t);
2529  if (!t)
2530  return;
2531 
2532  wxRect rect(GetVRulerOffset(),
2533  kTopMargin,
2534  GetVRulerWidth(),
2535  t->GetHeight() - (kTopMargin + kBottomMargin));
2536 
2537  mTrackArtist->UpdateVRuler(t, rect);
2538  const Track *l = t->GetLink();
2539  if (l)
2540  {
2541  rect.height = l->GetHeight() - (kTopMargin + kBottomMargin);
2542  mTrackArtist->UpdateVRuler(l, rect);
2543  }
2544 }
2545 
2547 {
2548  TrackListIterator iter(GetTracks());
2549  Track *t = iter.First();
2550  if (t) {
2551  wxSize s = t->vrulerSize;
2552  while (t) {
2553  s.IncTo(t->vrulerSize);
2554  t = iter.Next();
2555  }
2556  if (vrulerSize != s) {
2557  vrulerSize = s;
2558  mRuler->SetLeftOffset(GetLeftOffset()); // bevel on AdornedRuler
2559  mRuler->Refresh();
2560  }
2561  }
2562  Refresh(false);
2563 }
2564 
2565 // Make sure selection edge is in view
2567 {
2568  int w;
2569  GetTracksUsableArea( &w, NULL );
2570 
2571  int pixel = mViewInfo->TimeToPosition(pos);
2572  if (pixel < 0 || pixel >= w)
2573  {
2575  (mViewInfo->OffsetTimeByPixels(pos, -(w / 2)));
2576  Refresh(false);
2577  }
2578 }
2579 
2581 {
2583 }
2584 
2586 {
2587  if(!t) {
2588  t = GetFocusedTrack();
2589  if(!t)
2590  return;
2591  }
2592 
2593  const auto pCell = t->GetTrackControl();
2594  const wxRect rect(FindTrackRect(t, true));
2595  const UIHandle::Result refreshResult =
2596  pCell->DoContextMenu(rect, this, NULL);
2597  ProcessUIHandleResult(this, mRuler, t, t, refreshResult);
2598 }
2599 
2601 {
2602 
2603  TrackListIterator iter(GetTracks());
2604 
2605  Track * t;
2606  for ( t = iter.First();t!=NULL;t=iter.Next())
2607  {
2608  //Find the first selected track
2609  if(t->GetSelected())
2610  {
2611  return t;
2612  }
2613 
2614  }
2615  //if nothing is selected, return the first track
2616  t = iter.First();
2617 
2618  if(t)
2619  return t;
2620  else
2621  return NULL;
2622 }
2623 
2625 {
2626  TrackListIterator iter(GetTracks());
2627  Track *it = NULL;
2628  Track *nt = NULL;
2629 
2630  SetFocusedTrack(t);
2631 
2632  int trackTop = 0;
2633  int trackHeight =0;
2634 
2635  for (it = iter.First(); it; it = iter.Next()) {
2636  trackTop += trackHeight;
2637  trackHeight = it->GetHeight();
2638 
2639  //find the second track if this is stereo
2640  if (it->GetLinked()) {
2641  nt = iter.Next();
2642  trackHeight += nt->GetHeight();
2643  }
2644  else {
2645  nt = it;
2646  }
2647 
2648  //We have found the track we want to ensure is visible.
2649  if ((it == t) || (nt == t)) {
2650 
2651  //Get the size of the trackpanel.
2652  int width, height;
2653  GetSize(&width, &height);
2654 
2655  if (trackTop < mViewInfo->vpos) {
2656  height = mViewInfo->vpos - trackTop + mViewInfo->scrollStep;
2657  height /= mViewInfo->scrollStep;
2658  mListener->TP_ScrollUpDown(-height);
2659  }
2660  else if (trackTop + trackHeight > mViewInfo->vpos + height) {
2661  height = (trackTop + trackHeight) - (mViewInfo->vpos + height);
2662  height = (height + mViewInfo->scrollStep + 1) / mViewInfo->scrollStep;
2663  mListener->TP_ScrollUpDown(height);
2664  }
2665 
2666  break;
2667  }
2668  }
2669  Refresh(false);
2670 }
2671 
2672 // 0.0 scrolls to top
2673 // 1.0 scrolls to bottom.
2674 void TrackPanel::VerticalScroll( float fracPosition){
2675  TrackListIterator iter(GetTracks());
2676  Track *it = NULL;
2677  Track *nt = NULL;
2678 
2679  // Compute trackHeight
2680  int trackTop = 0;
2681  int trackHeight =0;
2682 
2683  for (it = iter.First(); it; it = iter.Next()) {
2684  trackTop += trackHeight;
2685  trackHeight = it->GetHeight();
2686 
2687  //find the second track if this is stereo
2688  if (it->GetLinked()) {
2689  nt = iter.Next();
2690  trackHeight += nt->GetHeight();
2691  }
2692  else {
2693  nt = it;
2694  }
2695  }
2696 
2697  int delta;
2698 
2699  //Get the size of the trackpanel.
2700  int width, height;
2701  GetSize(&width, &height);
2702 
2703  delta = (fracPosition * (trackTop + trackHeight - height)) - mViewInfo->vpos + mViewInfo->scrollStep;
2704  //wxLogDebug( "Scroll down by %i pixels", delta );
2705  delta /= mViewInfo->scrollStep;
2706  mListener->TP_ScrollUpDown(delta);
2707  Refresh(false);
2708 }
2709 
2710 
2711 // Given rectangle excludes the insets left, right, and top
2712 // Draw a rectangular border and also a vertical separator of track controls
2713 // from the rest (ruler and proper track area)
2715  const wxRect & rect, const int labelw,
2716  const int vrul)
2717 {
2718  // Border around track and label area
2719  // leaving room for the shadow
2720  dc->SetBrush(*wxTRANSPARENT_BRUSH);
2721  dc->SetPen(*wxBLACK_PEN);
2722  dc->DrawRectangle(rect.x, rect.y,
2723  rect.width - kShadowThickness,
2724  rect.height - kShadowThickness);
2725 
2726  // between vruler and TrackInfo
2727  AColor::Line(*dc, vrul, rect.y, vrul, rect.y + rect.height - 1);
2728 
2729  // The lines at bottom of 1st track and top of second track of stereo group
2730  // Possibly replace with DrawRectangle to add left border.
2731  if (t->GetLinked()) {
2732  // The given rect has had the top inset subtracted
2733  int h1 = rect.y + t->GetHeight() - kTopInset;
2734  // h1 is the top coordinate of the second tracks' rectangle
2735  // Draw (part of) the bottom border of the top channel and top border of the bottom
2736  // At left it extends between the vertical rulers too
2737  // These lines stroke over what is otherwise "border" of each channel
2738  AColor::Line(*dc, labelw, h1 - kBottomMargin, rect.x + rect.width - 1, h1 - kBottomMargin);
2739  AColor::Line(*dc, labelw, h1 + kTopInset, rect.x + rect.width - 1, h1 + kTopInset);
2740  }
2741 }
2742 
2743 // Given rectangle has insets subtracted left, right, and top
2744 // Stroke lines along bottom and right, which are slightly short at
2745 // bottom-left and top-right
2746 void TrackPanel::DrawShadow(Track * /* t */ , wxDC * dc, const wxRect & rect)
2747 {
2748  int right = rect.x + rect.width - 1;
2749  int bottom = rect.y + rect.height - 1;
2750 
2751  // shadow color for lines
2752  dc->SetPen(*wxBLACK_PEN);
2753 
2754  // bottom
2755  AColor::Line(*dc, rect.x, bottom, right, bottom);
2756  // right
2757  AColor::Line(*dc, right, rect.y, right, bottom);
2758 
2759  // background color erases small parts of those lines
2760  AColor::Dark(dc, false);
2761 
2762  // bottom-left
2763  AColor::Line(*dc, rect.x, bottom, rect.x + 1, bottom);
2764  // top-right
2765  AColor::Line(*dc, right, rect.y, right, rect.y + 1);
2766 }
2767 
2772 {
2773  auto range = Cells();
2774  auto &iter = range.first, &end = range.second;
2775  auto prev = iter;
2776  while
2777  ( iter != end &&
2778  !(*iter).second.Contains( mouseX, mouseY ) )
2779  prev = iter++;
2780  if ( iter == end )
2781  // Default to the background cell, which is always last in the sequence,
2782  // even if it does not contain the point
2783  iter = prev;
2784  auto found = *iter;
2785  return {
2786  static_cast<CommonTrackPanelCell*>( found.first.get() )->FindTrack(),
2787  found.first,
2788  found.second
2789  };
2790 }
2791 
2792 // This finds the rectangle of a given track (including all channels),
2793 // either that of the label 'adornment' or the track itself
2794 // The given track is assumed to be the first channel
2795 wxRect TrackPanel::FindTrackRect( const Track * target, bool label )
2796 {
2797  if (!target) {
2798  return { 0, 0, 0, 0 };
2799  }
2800 
2801  wxRect rect{
2802  0,
2803  target->GetY() - mViewInfo->vpos,
2804  GetSize().GetWidth(),
2805  target->GetHeight()
2806  };
2807 
2808  // PRL: I think the following very old comment misused the term "race
2809  // condition" for a bug that happened with only a single thread. I think the
2810  // real problem referred to, was that this function could be reached, via
2811  // TrackPanelAx callbacks, during low-level operations while the TrackList
2812  // was not in a consistent state. Therefore GetLinked() did not imply
2813  // that GetLink() was not null.
2814  // Now the problem is fixed by delaying the handling of events generated
2815  // by TrackList.
2816 
2817  // Old comment:
2818  // The check for a null linked track is necessary because there's
2819  // a possible race condition between the time the 2 linked tracks
2820  // are added and when wxAccessible methods are called. This is
2821  // most evident when using Jaws.
2822  if (target->GetLinked() && target->GetLink()) {
2823  rect.height += target->GetLink()->GetHeight();
2824  }
2825 
2826  rect.x += kLeftMargin;
2827  if (label)
2828  rect.width = GetVRulerOffset() - kLeftMargin;
2829  else
2830  rect.width -= (kLeftMargin + kRightMargin);
2831 
2832  rect.y += kTopMargin;
2833  rect.height -= (kTopMargin + kBottomMargin);
2834 
2835  return rect;
2836 }
2837 
2839 {
2840  return vrulerSize.x;
2841 }
2842 
2845 {
2846  if (!mListener)
2847  return;
2848 
2849  // DM: Note that the Selection Bar can actually MODIFY the selection
2850  // if snap-to mode is on!!!
2852 }
2853 
2855 {
2856  return mAx->GetFocus().get();
2857 }
2858 
2860 {
2861  // Make sure we always have the first linked track of a stereo track
2862  if (t && !t->GetLinked() && t->GetLink())
2863  t = (WaveTrack*)t->GetLink();
2864 
2867  }
2868 
2869  if (t) {
2871  }
2872 
2873  mAx->SetFocus( Track::Pointer( t ) );
2874  Refresh( false );
2875 }
2876 
2877 void TrackPanel::OnSetFocus(wxFocusEvent & WXUNUSED(event))
2878 {
2880  Refresh( false );
2881 }
2882 
2883 void TrackPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event))
2884 {
2886  {
2888  }
2889  Refresh( false);
2890 }
2891 
2892 /**********************************************************************
2893 
2894  TrackInfo code is destined to move out of this file.
2895  Code should become a lot cleaner when we have sizers.
2896 
2897 **********************************************************************/
2898 
2900 {
2901  pParent = pParentIn;
2902 
2903  ReCreateSliders();
2904 
2905  UpdatePrefs();
2906 }
2907 
2909 {
2910 }
2911 
2913  const wxPoint point{ 0, 0 };
2914  wxRect sliderRect;
2915  GetGainRect(point, sliderRect);
2916 
2917  float defPos = 1.0;
2918  /* i18n-hint: Title of the Gain slider, used to adjust the volume */
2919  gGain = std::make_unique<LWSlider>(pParent, _("Gain"),
2920  wxPoint(sliderRect.x, sliderRect.y),
2921  wxSize(sliderRect.width, sliderRect.height),
2922  DB_SLIDER);
2923  gGain->SetDefaultValue(defPos);
2924 
2925  gGainCaptured = std::make_unique<LWSlider>(pParent, _("Gain"),
2926  wxPoint(sliderRect.x, sliderRect.y),
2927  wxSize(sliderRect.width, sliderRect.height),
2928  DB_SLIDER);
2929  gGainCaptured->SetDefaultValue(defPos);
2930 
2931  GetPanRect(point, sliderRect);
2932 
2933  defPos = 0.0;
2934  /* i18n-hint: Title of the Pan slider, used to move the sound left or right */
2935  gPan = std::make_unique<LWSlider>(pParent, _("Pan"),
2936  wxPoint(sliderRect.x, sliderRect.y),
2937  wxSize(sliderRect.width, sliderRect.height),
2938  PAN_SLIDER);
2939  gPan->SetDefaultValue(defPos);
2940 
2941  gPanCaptured = std::make_unique<LWSlider>(pParent, _("Pan"),
2942  wxPoint(sliderRect.x, sliderRect.y),
2943  wxSize(sliderRect.width, sliderRect.height),
2944  PAN_SLIDER);
2945  gPanCaptured->SetDefaultValue(defPos);
2946 
2947 #ifdef EXPERIMENTAL_MIDI_OUT
2948  GetVelocityRect(point, sliderRect);
2949 
2950  /* i18n-hint: Title of the Velocity slider, used to adjust the volume of note tracks */
2951  gVelocity = std::make_unique<LWSlider>(pParent, _("Velocity"),
2952  wxPoint(sliderRect.x, sliderRect.y),
2953  wxSize(sliderRect.width, sliderRect.height),
2954  VEL_SLIDER);
2955  gVelocity->SetDefaultValue(0.0);
2956  gVelocityCaptured = std::make_unique<LWSlider>(pParent, _("Velocity"),
2957  wxPoint(sliderRect.x, sliderRect.y),
2958  wxSize(sliderRect.width, sliderRect.height),
2959  VEL_SLIDER);
2960  gVelocityCaptured->SetDefaultValue(0.0);
2961 #endif
2962 
2963 }
2964 
2966 {
2967  return kTrackInfoWidth;
2968 }
2969 
2970 void TrackInfo::GetCloseBoxHorizontalBounds( const wxRect & rect, wxRect &dest )
2971 {
2972  dest.x = rect.x;
2973  dest.width = kTrackInfoBtnSize;
2974 }
2975 
2976 void TrackInfo::GetCloseBoxRect(const wxRect & rect, wxRect & dest)
2977 {
2978  GetCloseBoxHorizontalBounds( rect, dest );
2979  auto results = CalcItemY( commonTrackTCPLines, kItemBarButtons );
2980  dest.y = rect.y + results.first;
2981  dest.height = results.second;
2982 }
2983 
2984 static const int TitleSoloBorderOverlap = 1;
2985 
2986 void TrackInfo::GetTitleBarHorizontalBounds( const wxRect & rect, wxRect &dest )
2987 {
2988  // to right of CloseBoxRect, plus a little more
2989  wxRect closeRect;
2990  GetCloseBoxHorizontalBounds( rect, closeRect );
2991  dest.x = rect.x + closeRect.width + 1;
2992  dest.width = rect.x + rect.width - dest.x + TitleSoloBorderOverlap;
2993 }
2994 
2995 void TrackInfo::GetTitleBarRect(const wxRect & rect, wxRect & dest)
2996 {
2997  GetTitleBarHorizontalBounds( rect, dest );
2998  auto results = CalcItemY( commonTrackTCPLines, kItemBarButtons );
2999  dest.y = rect.y + results.first;
3000  dest.height = results.second;
3001 }
3002 
3003 void TrackInfo::GetNarrowMuteHorizontalBounds( const wxRect & rect, wxRect &dest )
3004 {
3005  dest.x = rect.x;
3006  dest.width = rect.width / 2 + 1;
3007 }
3008 
3009 void TrackInfo::GetNarrowSoloHorizontalBounds( const wxRect & rect, wxRect &dest )
3010 {
3011  wxRect muteRect;
3012  GetNarrowMuteHorizontalBounds( rect, muteRect );
3013  dest.x = rect.x + muteRect.width;
3014  dest.width = rect.width - muteRect.width + TitleSoloBorderOverlap;
3015 }
3016 
3017 void TrackInfo::GetWideMuteSoloHorizontalBounds( const wxRect & rect, wxRect &dest )
3018 {
3019  // Larger button, symmetrically placed intended.
3020  // On windows this gives 15 pixels each side.
3021  dest.width = rect.width - 2 * kTrackInfoBtnSize + 6;
3022  dest.x = rect.x + kTrackInfoBtnSize -3;
3023 }
3024 
3026 (const wxRect & rect, wxRect & dest, bool solo, bool bHasSoloButton,
3027  const Track *pTrack)
3028 {
3029 
3030  auto resultsM = CalcItemY( getTCPLines( *pTrack ), kItemMute );
3031  auto resultsS = CalcItemY( getTCPLines( *pTrack ), kItemSolo );
3032  dest.height = resultsS.second;
3033 
3034  int yMute = resultsM.first;
3035  int ySolo = resultsS.first;
3036 
3037  bool bSameRow = ( yMute == ySolo );
3038  bool bNarrow = bSameRow && bHasSoloButton;
3039 
3040  if( bNarrow )
3041  {
3042  if( solo )
3043  GetNarrowSoloHorizontalBounds( rect, dest );
3044  else
3045  GetNarrowMuteHorizontalBounds( rect, dest );
3046  }
3047  else
3048  GetWideMuteSoloHorizontalBounds( rect, dest );
3049 
3050  if( bSameRow || !solo )
3051  dest.y = rect.y + yMute;
3052  else
3053  dest.y = rect.y + ySolo;
3054 
3055 }
3056 
3057 void TrackInfo::GetSliderHorizontalBounds( const wxPoint &topleft, wxRect &dest )
3058 {
3059  dest.x = topleft.x + 6;
3060  dest.width = kTrackInfoSliderWidth;
3061 }
3062 
3063 void TrackInfo::GetGainRect(const wxPoint &topleft, wxRect & dest)
3064 {
3065  GetSliderHorizontalBounds( topleft, dest );
3066  auto results = CalcItemY( waveTrackTCPLines, kItemGain );
3067  dest.y = topleft.y + results.first;
3068  dest.height = results.second;
3069 }
3070 
3071 void TrackInfo::GetPanRect(const wxPoint &topleft, wxRect & dest)
3072 {
3073  GetGainRect( topleft, dest );
3074  auto results = CalcItemY( waveTrackTCPLines, kItemPan );
3075  dest.y = topleft.y + results.first;
3076 }
3077 
3078 #ifdef EXPERIMENTAL_MIDI_OUT
3079 void TrackInfo::GetVelocityRect(const wxPoint &topleft, wxRect & dest)
3080 {
3081  GetSliderHorizontalBounds( topleft, dest );
3082  auto results = CalcItemY( noteTrackTCPLines, kItemVelocity );
3083  dest.y = topleft.y + results.first;
3084  dest.height = results.second;
3085 }
3086 #endif
3087 
3088 void TrackInfo::GetMinimizeHorizontalBounds( const wxRect &rect, wxRect &dest )
3089 {
3090  const int space = 0;// was 3.
3091  dest.x = rect.x + space;
3092 
3093  wxRect syncLockRect;
3094  GetSyncLockHorizontalBounds( rect, syncLockRect );
3095 
3096  // Width is rect.width less space on left for track select
3097  // and on right for sync-lock icon.
3098  dest.width = rect.width - (space + syncLockRect.width);
3099 }
3100 
3101 void TrackInfo::GetMinimizeRect(const wxRect & rect, wxRect &dest)
3102 {
3103  GetMinimizeHorizontalBounds( rect, dest );
3104  auto results = CalcBottomItemY
3105  ( commonTrackTCPBottomLines, kItemMinimize, rect.height);
3106  dest.y = rect.y + results.first;
3107  dest.height = results.second;
3108 }
3109 
3110 void TrackInfo::GetSyncLockHorizontalBounds( const wxRect &rect, wxRect &dest )
3111 {
3112  dest.width = kTrackInfoBtnSize;
3113  dest.x = rect.x + rect.width - dest.width;
3114 }
3115 
3116 void TrackInfo::GetSyncLockIconRect(const wxRect & rect, wxRect &dest)
3117 {
3118  GetSyncLockHorizontalBounds( rect, dest );
3119  auto results = CalcBottomItemY
3120  ( commonTrackTCPBottomLines, kItemSyncLock, rect.height);
3121  dest.y = rect.y + results.first;
3122  dest.height = results.second;
3123 }
3124 
3125 #ifdef USE_MIDI
3126 void TrackInfo::GetMidiControlsHorizontalBounds
3127 ( const wxRect &rect, wxRect &dest )
3128 {
3129  dest.x = rect.x + 1; // To center slightly
3130  // PRL: TODO: kMidiCellWidth is defined in terms of the other constant
3131  // kTrackInfoWidth but I am trying to avoid use of that constant.
3132  // Can cell width be computed from dest.width instead?
3133  dest.width = kMidiCellWidth * 4;
3134 }
3135 
3136 void TrackInfo::GetMidiControlsRect(const wxRect & rect, wxRect & dest)
3137 {
3138  GetMidiControlsHorizontalBounds( rect, dest );
3139  auto results = CalcItemY( noteTrackTCPLines, kItemMidiControlsRect );
3140  dest.y = rect.y + results.first;
3141  dest.height = results.second;
3142 }
3143 #endif
3144 
3145 wxFont TrackInfo::gFont;
3146 
3149 {
3150  dc->SetFont(gFont);
3151 }
3152 
3154  ( wxDC* dc, const wxRect & rect, const Track &track ) const
3155 {
3156  AColor::Dark(dc, false); // same color as border of toolbars (ToolBar::OnPaint())
3157 
3158  // below close box and title bar
3159  wxRect buttonRect;
3160  GetTitleBarRect( rect, buttonRect );
3161  AColor::Line
3162  (*dc, rect.x, buttonRect.y + buttonRect.height,
3163  rect.width - 1, buttonRect.y + buttonRect.height);
3164 
3165  // between close box and title bar
3166  AColor::Line
3167  (*dc, buttonRect.x, buttonRect.y,
3168  buttonRect.x, buttonRect.y + buttonRect.height - 1);
3169 
3170  GetMuteSoloRect( rect, buttonRect, false, true, &track );
3171 
3172  bool bHasMuteSolo = dynamic_cast<const PlayableTrack*>( &track ) != NULL;
3173  if( bHasMuteSolo && !TrackInfo::HideTopItem( rect, buttonRect ) )
3174  {
3175  // above mute/solo
3176  AColor::Line
3177  (*dc, rect.x, buttonRect.y,
3178  rect.width - 1, buttonRect.y);
3179 
3180  // between mute/solo
3181  // Draw this little line; if there is no solo, wide mute button will
3182  // overpaint it later:
3183  AColor::Line
3184  (*dc, buttonRect.x + buttonRect.width, buttonRect.y,
3185  buttonRect.x + buttonRect.width, buttonRect.y + buttonRect.height - 1);
3186 
3187  // below mute/solo
3188  AColor::Line
3189  (*dc, rect.x, buttonRect.y + buttonRect.height,
3190  rect.width - 1, buttonRect.y + buttonRect.height);
3191  }
3192 
3193  // left of and above minimize button
3194  wxRect minimizeRect;
3195  this->GetMinimizeRect(rect, minimizeRect);
3196  AColor::Line
3197  (*dc, minimizeRect.x - 1, minimizeRect.y,
3198  minimizeRect.x - 1, minimizeRect.y + minimizeRect.height - 1);
3199  AColor::Line
3200  (*dc, minimizeRect.x, minimizeRect.y - 1,
3201  minimizeRect.x + minimizeRect.width - 1, minimizeRect.y - 1);
3202 }
3203 
3204 //#define USE_BEVELS
3205 
3206 // Paint the whole given rectangle some fill color
3207 void TrackInfo::DrawBackground(wxDC * dc, const wxRect & rect, bool bSelected,
3208  bool bHasMuteSolo, const int labelw, const int vrul) const
3209 {
3210  //compiler food.
3211  bHasMuteSolo;
3212  vrul;
3213 
3214  // fill in label
3215  wxRect fill = rect;
3216  fill.width = labelw-4;
3217  AColor::MediumTrackInfo(dc, bSelected);
3218  dc->DrawRectangle(fill);
3219 
3220 #ifdef USE_BEVELS
3221  // This branch is not now used
3222  // PRL: todo: banish magic numbers
3223  if( bHasMuteSolo )
3224  {
3225  int ylast = rect.height-20;
3226  int ybutton = wxMin(32,ylast-17);
3227  int ybuttonEnd = 67;
3228 
3229  fill=wxRect( rect.x+1, rect.y+17, vrul-6, ybutton);
3230  AColor::BevelTrackInfo( *dc, true, fill );
3231 
3232  if( ybuttonEnd < ylast ){
3233  fill=wxRect( rect.x+1, rect.y+ybuttonEnd, fill.width, ylast - ybuttonEnd);
3234  AColor::BevelTrackInfo( *dc, true, fill );
3235  }
3236  }
3237  else
3238  {
3239  fill=wxRect( rect.x+1, rect.y+17, vrul-6, rect.height-37);
3240  AColor::BevelTrackInfo( *dc, true, fill );
3241  }
3242 #endif
3243 }
3244 
3245 namespace {
3246 unsigned DefaultTrackHeight( const TCPLines &topLines )
3247 {
3248  int needed =
3250  totalTCPLines( topLines, true ) +
3251  totalTCPLines( commonTrackTCPBottomLines, false ) + 1;
3252  return (unsigned) std::max( needed, (int) Track::DefaultHeight );
3253 }
3254 }
3255 
3257 {
3258  return DefaultTrackHeight( noteTrackTCPLines );
3259 }
3260 
3262 {
3263  return DefaultTrackHeight( waveTrackTCPLines );
3264 }
3265 
3266 std::unique_ptr<LWSlider>
3270  , TrackInfo::gPan
3271 #ifdef EXPERIMENTAL_MIDI_OUT
3272  , TrackInfo::gVelocityCaptured
3273  , TrackInfo::gVelocity
3274 #endif
3275 ;
3276 
3278 (const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
3279 {
3280  wxPoint pos = sliderRect.GetPosition();
3281  float gain = t ? t->GetGain() : 1.0;
3282 
3283  gGain->Move(pos);
3284  gGain->Set(gain);
3285  gGainCaptured->Move(pos);
3286  gGainCaptured->Set(gain);
3287 
3288  auto slider = (captured ? gGainCaptured : gGain).get();
3289  slider->SetParent( pParent ? pParent : ::GetActiveProject() );
3290  return slider;
3291 }
3292 
3294 (const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
3295 {
3296  wxPoint pos = sliderRect.GetPosition();
3297  float pan = t ? t->GetPan() : 0.0;
3298 
3299  gPan->Move(pos);
3300  gPan->Set(pan);
3301  gPanCaptured->Move(pos);
3302  gPanCaptured->Set(pan);
3303 
3304  auto slider = (captured ? gPanCaptured : gPan).get();
3305  slider->SetParent( pParent ? pParent : ::GetActiveProject() );
3306  return slider;
3307 }
3308 
3309 #ifdef EXPERIMENTAL_MIDI_OUT
3310 LWSlider * TrackInfo::VelocitySlider
3311 (const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow *pParent)
3312 {
3313  wxPoint pos = sliderRect.GetPosition();
3314  float velocity = t ? t->GetVelocity() : 0.0;
3315 
3316  gVelocity->Move(pos);
3317  gVelocity->Set(velocity);
3318  gVelocityCaptured->Move(pos);
3319  gVelocityCaptured->Set(velocity);
3320 
3321  auto slider = (captured ? gVelocityCaptured : gVelocity).get();
3322  slider->SetParent( pParent ? pParent : ::GetActiveProject() );
3323  return slider;
3324 }
3325 #endif
3326 
3328 {
3329  // Calculation of best font size depends on language, so it should be redone in case
3330  // the language preference changed.
3331 
3332  int fontSize = 10;
3333  gFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
3334 
3335  int allowableWidth = GetTrackInfoWidth() - 2; // 2 to allow for left/right borders
3336  int textWidth, textHeight;
3337  do {
3338  gFont.SetPointSize(fontSize);
3339  pParent->GetTextExtent(_("Stereo, 999999Hz"),
3340  &textWidth,
3341  &textHeight,
3342  NULL,
3343  NULL,
3344  &gFont);
3345  fontSize--;
3346  } while (textWidth >= allowableWidth);
3347 }
3348 
3350 {
3351  return {
3352  TrackPanelCellIterator( this, true ),
3353  TrackPanelCellIterator( this, false )
3354  };
3355 }
3356 
3358  : mPanel{ trackPanel }
3359  , mIter{ trackPanel->GetProject() }
3360 {
3361  if (begin) {
3362  mpTrack = Track::Pointer( mIter.First() );
3363  if (mpTrack)
3364  mpCell = mpTrack;
3365  else
3366  mpCell = trackPanel->GetBackgroundCell();
3367  }
3368  else
3369  mDidBackground = true;
3370 
3371  const auto size = mPanel->GetSize();
3372  mRect = { 0, 0, size.x, size.y };
3373  UpdateRect();
3374 }
3375 
3377 {
3378  if ( mpTrack ) {
3379  if ( ++ mType == CellType::Background )
3381  }
3382  if ( mpTrack ) {
3383  if ( mType == CellType::Label &&
3384  mpTrack->GetLink() && !mpTrack->GetLinked() )
3385  // Visit label of stereo track only once
3386  ++mType;
3387  switch ( mType ) {
3388  case CellType::Track:
3389  mpCell = mpTrack;
3390  break;
3391  case CellType::Label:
3392  mpCell = mpTrack->GetTrackControl();
3393  break;
3394  case CellType::VRuler:
3395  mpCell = mpTrack->GetVRulerControl();
3396  break;
3397  case CellType::Resizer: {
3398  mpCell = mpTrack->GetResizer();
3399  break;
3400  }
3401  default:
3402  // should not happen
3403  mpCell.reset();
3404  break;
3405  }
3406  }
3407  else if ( !mDidBackground )
3409  else
3410  mpCell.reset();
3411 
3412  UpdateRect();
3413 
3414  return *this;
3415 }
3416 
3418 {
3419  TrackPanelCellIterator copy(*this);
3420  ++ *this;
3421  return copy;
3422 }
3423 
3424 auto TrackPanelCellIterator::operator* () const -> value_type
3425 {
3426  return { mpCell, mRect };
3427 }
3428 
3430 {
3431  const auto size = mPanel->GetSize();
3432  if ( mpTrack ) {
3433  mRect = {
3434  0,
3435  mpTrack->GetY() - mPanel->GetViewInfo()->vpos,
3436  size.x,
3437  mpTrack->GetHeight()
3438  };
3439  switch ( mType ) {
3440  case CellType::Track:
3441  mRect.x = mPanel->GetLeftOffset();
3442  mRect.width -= (mRect.x + kRightMargin);
3443  mRect.y += kTopMargin;
3444  mRect.height -= (kBottomMargin + kTopMargin);
3445  break;
3446  case CellType::Label: {
3447  mRect.x = kLeftMargin;
3448  mRect.width = kTrackInfoWidth - mRect.x;
3449  mRect.y += kTopMargin;
3450  mRect.height -= (kBottomMargin + kTopMargin);
3451  auto partner = mpTrack->GetLink();
3452  if ( partner && mpTrack->GetLinked() )
3453  mRect.height += partner->GetHeight();
3454  break;
3455  }
3456  case CellType::VRuler:
3457  {
3458  mRect.x = kTrackInfoWidth;
3459  // Right edge of the VRuler is inactive.
3460  mRect.width = mPanel->GetLeftOffset() - mRect.x;
3461  mRect.y += kTopMargin;
3462  mRect.height -= (kBottomMargin + kTopMargin);
3463  }
3464  break;
3465  case CellType::Resizer: {
3466  // The resizer region encompasses the bottom margin proper to this
3467  // track, plus the top margin of the next track (or, an equally
3468  // tall zone below, in case there is no next track)
3469  auto partner = mpTrack->GetLink();
3470  if ( partner && mpTrack->GetLinked() )
3471  mRect.x = kTrackInfoWidth;
3472  else
3473  mRect.x = kLeftMargin;
3474  mRect.width -= (mRect.x + kRightMargin);
3475  mRect.y += (mRect.height - kBottomMargin);
3476  mRect.height = (kBottomMargin + kTopMargin);
3477  break;
3478  }
3479  default:
3480  // should not happen
3481  break;
3482  }
3483  }
3484  else if ( mpCell ) {
3485  // Find a disjoint, maybe empty, rectangle
3486  // for the empty space appearing at bottom
3487 
3488  mRect.x = kLeftMargin;
3489  mRect.width = size.x - (mRect.x + kRightMargin);
3490 
3491  // Use previous value of the bottom, either the whole area if
3492  // there were no tracks, or else the resizer of the last track
3493  mRect.y =
3494  std::min( size.y,
3495  std::max( 0,
3496  mRect.y + mRect.height ) );
3497  mRect.height = size.y - mRect.y;
3498  }
3499  else
3500  mRect = {};
3501 }
3502 
3503 static TrackPanel * TrackPanelFactory(wxWindow * parent,
3504  wxWindowID id,
3505  const wxPoint & pos,
3506  const wxSize & size,
3507  const std::shared_ptr<TrackList> &tracks,
3508  ViewInfo * viewInfo,
3509  TrackPanelListener * listener,
3510  AdornedRulerPanel * ruler)
3511 {
3512  wxASSERT(parent); // to justify safenew
3513  return safenew TrackPanel(
3514  parent,
3515  id,
3516  pos,
3517  size,
3518  tracks,
3519  viewInfo,
3520  listener,
3521  ruler);
3522 }
3523 
3524 
3525 // Declare the static factory function.
3526 // We defined it in the class.
3527 TrackPanel *(*TrackPanel::FactoryFunction)(
3528  wxWindow * parent,
3529  wxWindowID id,
3530  const wxPoint & pos,
3531  const wxSize & size,
3532  const std::shared_ptr<TrackList> &tracks,
3533  ViewInfo * viewInfo,
3534  TrackPanelListener * listener,
3536 
3538 {
3539 }
3540 
3543 {
3544  return RefreshCode::Cancelled;
3545 }
3546 
3548  (const wxRect &, wxWindow*, wxPoint *)
3549 {
3550  return RefreshCode::RefreshNone;
3551 }
3552 
3553 unsigned TrackPanelCell::CaptureKey(wxKeyEvent &event, ViewInfo &, wxWindow *)
3554 {
3555  event.Skip();
3556  return RefreshCode::RefreshNone;
3557 }
3558 
3559 unsigned TrackPanelCell::KeyDown(wxKeyEvent &event, ViewInfo &, wxWindow *)
3560 {
3561  event.Skip();
3562  return RefreshCode::RefreshNone;
3563 }
3564 
3565 unsigned TrackPanelCell::KeyUp(wxKeyEvent &event, ViewInfo &, wxWindow *)
3566 {
3567  event.Skip();
3568  return RefreshCode::RefreshNone;
3569 }
3570 
3571 unsigned TrackPanelCell::Char(wxKeyEvent &event, ViewInfo &, wxWindow *)
3572 {
3573  event.Skip();
3574  return RefreshCode::RefreshNone;
3575 }
void SetZoom(double pixelsPerSecond)
Definition: ViewInfo.cpp:94
#define MUTE_SOLO_ITEMS(extra)
void RefreshTrack(Track *trk, bool refreshbacking=true)
TrackPanelCellIterator(TrackPanel *trackPanel, bool begin)
size_t GetTrackCount() const
static void SetTrackInfoFont(wxDC *dc)
int GetLabelWidth() const
Definition: TrackPanel.h:395
static void GetCloseBoxHorizontalBounds(const wxRect &rect, wxRect &dest)
void RedrawProject(const bool bForceWaveTracks=false)
Definition: Project.cpp:1356
void HandleClick(const TrackPanelMouseEvent &tpmEvent)
value_type operator*() const
static wxFont gFont
Definition: TrackPanel.h:239
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:215
void Uncapture(wxMouseState *pState=nullptr)
Definition: TrackPanel.cpp:720
void UpdateViewIfNoTracks()
const TrackList * GetTracks() const
Definition: TrackPanel.h:400
bool bUpdateTrackIndicator
Definition: ViewInfo.h:182
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:88
static void GetWideMuteSoloHorizontalBounds(const wxRect &rect, wxRect &dest)
static void WideMuteDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
void EnsureVisible(Track *t)
A now badly named class which is used to give access to a subset of the TrackPanel methods from all o...
SelectedRegion selectedRegion
Definition: ViewInfo.h:160
void OnTrackListDeletion(wxCommandEvent &event)
static void CaptureKeyboard(wxWindow *handler)
Definition: Project.cpp:5764
std::shared_ptr< Track > mpTrack
void OnKeyUp(wxKeyEvent &event)
void DrawEverythingElse(TrackPanelDrawingContext &context, const wxRegion &region, const wxRect &clip)
std::shared_ptr< TrackList > mTracks
Definition: TrackPanel.h:462
bool GetSelected() const
Definition: Track.h:217
bool IsStreamActive()
Returns true if the audio i/o is running at all, but not during cleanup.
Definition: AudioIO.cpp:2898
static void TrackPanelBackground(wxDC *dc, bool selected)
Definition: AColor.cpp:349
const Track * First(const TrackList *val=NULL)
Definition: Track.h:402
static void DrawItems(TrackPanelDrawingContext &context, const wxRect &rect, const Track &track)
static void Arrow(wxDC &dc, wxCoord x, wxCoord y, int width, bool down=true)
Definition: AColor.cpp:96
virtual unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent)
virtual void TP_ModifyState(bool bWantsAutoSave)=0
static void Bevel2(wxDC &dc, bool up, const wxRect &r, bool bSel=false, bool bHighlight=false)
Definition: AColor.cpp:222
TrackPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, const std::shared_ptr< TrackList > &tracks, ViewInfo *viewInfo, TrackPanelListener *listener, AdornedRulerPanel *ruler)
Definition: TrackPanel.cpp:297
void OnTrackMenu(Track *t=NULL)
double GetScreenEndTime() const
Definition: TrackPanel.cpp:565
static void GetNarrowSoloHorizontalBounds(const wxRect &rect, wxRect &dest)
float GetPan() const
Definition: WaveTrack.cpp:424
DEFINE_EVENT_TYPE(EVT_OPEN_AUDIO_FILE)
Custom events.
void HandlePageDownKey()
Definition: TrackPanel.cpp:810
bool IsSyncLockSelected() const
Definition: Track.cpp:232
#define STATUS_ITEMS
void DisplaySelection()
Displays the bounds of the selection in the status bar.
virtual void TP_HandleResize()=0
wxRect FindTrackRect(const Track *target, bool label)
virtual unsigned HandleWheelRotation(const TrackPanelMouseEvent &event, AudacityProject *pProject)
int mMouseMostRecentX
Definition: TrackPanel.h:499
void UpdatePrefs()
Definition: TrackPanel.cpp:411
void HandlePageUpKey()
Definition: TrackPanel.cpp:805
void SetAudioIOToken(int token)
Definition: Project.cpp:1427
unsigned GetNumCaptureChannels() const
Definition: AudioIO.h:373
static void MuteOrSoloDrawFunction(wxDC *dc, const wxRect &rect, const Track *pTrack, bool down, bool captured, bool solo, bool hit)
wxString label
Definition: Tags.cpp:727
TrackPanel::AudacityTimer mTimer
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
void OnTrackListResizing(wxCommandEvent &event)
static bool HasSoloButton()
Definition: TrackPanel.h:442
double OffsetTimeByPixels(double time, wxInt64 offset, bool ignoreFisheye=false) const
Definition: ViewInfo.h:77
virtual int GetChannel() const
Definition: Track.h:223
SelectedRegion mLastDrawnSelectedRegion
Definition: TrackPanel.h:520
bool GetLinked() const
Definition: Track.h:218
bool IsAudioTokenActive(int token)
Returns true if the stream is active, or even if audio I/O is busy cleaning up its data or writing to...
Definition: AudioIO.cpp:2917
unsigned mMouseOverUpdateFlags
Definition: TrackPanel.h:529
void HighlightFocusedTrack(wxDC *dc, const wxRect &rect)
Draw a three-level highlight gradient around the focused track.
void StopPlaying(bool stopStream=true)
int GetAudioIOToken() const
Definition: Project.cpp:1422
int GetVRulerWidth() const
static std::unique_ptr< LWSlider > gPanCaptured
Definition: TrackPanel.h:242
static void MediumTrackInfo(wxDC *dc, bool selected)
Definition: AColor.cpp:328
void DrawSelection()
Definition: Ruler.cpp:3176
bool GetMinimized() const
Definition: Track.cpp:179
size_t GetSelectedTrackCount() const
void OnCaptureKey(wxCommandEvent &event)
virtual void TP_ScrollWindow(double scrollto)=0
double h
Definition: ViewInfo.h:47
static void GetCloseBoxRect(const wxRect &rect, wxRect &dest)
bool mEnableTab
Definition: TrackPanel.h:561
TrackPanelCellIterator & operator++()
static wxString gSoloPref
Definition: TrackPanel.h:515
virtual ~TrackPanel()
Definition: TrackPanel.cpp:359
void UpdateSelectionDisplay()
bool IsMouseCaptured()
Determines if a modal tool is active.
static void Dark(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:338
#define RANGE(array)
static unsigned MinimumTrackHeight()
void ScrollIntoView(double pos)
virtual unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent)
#define safenew
Definition: Audacity.h:223
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:274
void ReCreateSliders()
virtual unsigned DoContextMenu(const wxRect &rect, wxWindow *pParent, wxPoint *pPosition)
void UpdatePrefs()
Definition: ViewInfo.cpp:150
UIHandlePtr Target()
Definition: TrackPanel.h:532
static void Status1DrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
static unsigned DefaultWaveTrackHeight()
void ClearTargets()
Definition: TrackPanel.h:541
virtual int GetKind() const
Definition: Track.h:267
static void CloseTitleDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
Track * Next(bool skiplinked=false) override
Definition: Track.cpp:513
const Track * Next(bool skiplinked=false)
Definition: Track.h:406
std::shared_ptr< Track > GetTrack() const
Definition: ButtonHandle.h:28
void UpdateAccessibility()
void SetFocusedTrack(Track *t)
void OnChar(wxKeyEvent &event)
void GetTracksUsableArea(int *width, int *height) const
Definition: TrackPanel.cpp:438
std::shared_ptr< NoteTrack > GetTrack() const
void DrawOutsideOfTrack(TrackPanelDrawingContext &context, Track *t, const wxRect &rect)
ViewInfo * mViewInfo
Definition: TrackPanel.h:463
void DisplayBitmap(wxDC &dc)
Definition: BackedPanel.cpp:65
TrackList is a flat linked list of tracks supporting Add, Remove, Clear, and Contains, plus serialization of the list of tracks.
Definition: Track.h:530
void FixScrollbars()
Definition: Project.cpp:1872
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:161
static LWSlider * PanSlider(const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
wxSize vrulerSize
Definition: TrackPanel.h:523
unsigned Result
Definition: UIHandle.h:37
Track * GetFirstSelectedTrack()
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
#define PAN_SLIDER
Definition: ASlider.h:45
static void Mute(wxDC *dc, bool on, bool selected, bool soloing)
Definition: AColor.cpp:398
int format
Definition: ExportPCM.cpp:56
void DrawOutside(TrackPanelDrawingContext &context, Track *t, const wxRect &rec)
static unsigned DefaultNoteTrackHeight()
bool HandleEscapeKey(bool down)
Definition: TrackPanel.cpp:754
static void WideSoloDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
Defines a selected portion of a project.
void UpdatePrefs()
void OnTimer(wxTimerEvent &event)
AS: This gets called on our wx timer events.
Definition: TrackPanel.cpp:489
#define COMMON_ITEMS
UIHandlePtr mUIHandle
Definition: TrackPanel.h:557
IteratorRange< TrackPanelCellIterator > Cells()
The TrackPanel class coordinates updates and operations on the main part of the screen which contains...
Definition: TrackPanel.h:257
static void Solo(wxDC *dc, bool on, bool selected)
Definition: AColor.cpp:413
TrackPanel * pParent
Definition: TrackPanel.h:238
const int kTimerInterval
void RepairBitmap(wxDC &dc, wxCoord x, wxCoord y, wxCoord width, wxCoord height)
Definition: BackedPanel.cpp:60
static const int TitleSoloBorderOverlap
static void MinimizeSyncLockDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
Lightweight version of ASlider. In other words it does not have a window permanently associated with ...
Definition: ASlider.h:72
void DrawBordersAroundTrack(Track *t, wxDC *dc, const wxRect &rect, const int labelw, const int vrul)
static void PanSliderDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
static void GetGainRect(const wxPoint &topLeft, wxRect &dest)
virtual void TP_DisplayStatusMessage(const wxString &msg)=0
std::shared_ptr< Track > GetFirstVisible()
Definition: Project.cpp:2022
bool IsAudioActive()
Definition: TrackPanel.cpp:830
std::shared_ptr< TrackPanelCell > GetTrackControl()
Definition: TrackUI.cpp:81
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
void DrawTracks(wxDC *dc)
static void GetNarrowMuteHorizontalBounds(const wxRect &rect, wxRect &dest)
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:67
void HandleInterruptedDrag()
Definition: TrackPanel.cpp:634
void DrawOverlays(bool repaint_all, wxDC *pDC=nullptr)
DrawFunction drawFunction
wxImage & Image(int iIndex)
Definition: Theme.cpp:1240
bool CancelDragging()
Definition: TrackPanel.cpp:734
int GetTrackInfoWidth() const
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ViewInfo.cpp:59
static void GetTitleBarHorizontalBounds(const wxRect &rect, wxRect &dest)
TrackInfo mTrackInfo
Definition: TrackPanel.h:446
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
bool HasEscape()
Definition: TrackPanel.cpp:978
int min(int a, int b)
wxString GetName() const
Definition: Track.h:212
double GetMostRecentXPos()
void OnCaptureLost(wxMouseCaptureLostEvent &event)
Should handle the case when the mouse capture is lost.
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:355
LWSlider * PanSlider(const WaveTrack *wt)
Definition: TrackPanel.cpp:391
static void GetSliderHorizontalBounds(const wxPoint &topleft, wxRect &dest)
virtual void TP_RedrawScrollbars()=0
static void MuteAndSoloDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
void UpdateMouseState(const wxMouseState &state)
Definition: TrackPanel.cpp:780
FoundCell FindCell(int mouseX, int mouseY)
static void GetMinimizeHorizontalBounds(const wxRect &rect, wxRect &dest)
bool IsAudioActive() const
Definition: Project.cpp:1432
int scrollStep
Definition: ViewInfo.h:177
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:252
void UpdateTrackVRuler(const Track *t)
void OnContextMenu(wxContextMenuEvent &event)
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:662
void SetBackgroundCell(const std::shared_ptr< TrackPanelCell > &pCell)
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:122
virtual ~TrackPanelCell()=0
static void GetSyncLockIconRect(const wxRect &rect, wxRect &dest)
An AudioTrack that can be played and stopped.
Definition: Track.h:313
wxDC & GetBackingDCForRepaint()
Definition: BackedPanel.cpp:35
void DrawShadow(Track *t, wxDC *dc, const wxRect &rect)
std::shared_ptr< TrackPanelCell > mpCell
void VerticalScroll(float fracPosition)
static void GetMinimizeRect(const wxRect &rect, wxRect &dest)
int vpos
Definition: ViewInfo.h:45
TrackPanelListener * mListener
Definition: TrackPanel.h:460
static bool HideTopItem(const wxRect &rect, const wxRect &subRect, int allowance=0)
virtual unsigned KeyUp(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent)
static void GetTitleBarRect(const wxRect &rect, wxRect &dest)
const wxChar * GetSampleFormatStr(sampleFormat format)
An iterator for a TrackList.
Definition: Track.h:339
static void GetMuteSoloRect(const wxRect &rect, wxRect &dest, bool solo, bool bHasSoloButton, const Track *pTrack)
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
std::shared_ptr< TrackPanelCell > GetBackgroundCell()
#define DB_SLIDER
Definition: ASlider.h:44
static void ReleaseKeyboard(wxWindow *handler)
Definition: Project.cpp:5775
#define VEL_SLIDER
Definition: ASlider.h:48
void UpdateVRuler(Track *t)
void HandleWheelRotation(TrackPanelMouseEvent &tpmEvent)
Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling)
bool HasRotation()
Definition: TrackPanel.cpp:969
size_t mTarget
Definition: TrackPanel.h:528
AudioIO * gAudioIO
Definition: AudioIO.cpp:481
int mTimeCount
Definition: TrackPanel.h:485
static void TrackFocusPen(wxDC *dc, int level)
Definition: AColor.cpp:384
ControlToolBar * GetControlToolBar()
Definition: Project.cpp:4787
void MessageForScreenReader(const wxString &message)
TrackPanelListener * GetListener()
Definition: TrackPanel.h:403
int mMouseMostRecentY
Definition: TrackPanel.h:500
void DrawBackground(wxDC *dc, const wxRect &rect, bool bSelected, bool bHasMuteSolo, const int labelw, const int vrul) const
bool IsDown(int tool) const
void MakeParentModifyState(bool bWantsAutoSave)
Definition: TrackPanel.cpp:624
Track * GetLink() const
Definition: Track.cpp:204
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:396
std::unique_ptr< TrackArtist > mTrackArtist
Definition: TrackPanel.h:467
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:302
static void GainSliderDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
static double GetDefaultZoom()
Definition: ViewInfo.h:85
std::shared_ptr< TrackPanelCell > pCell
int GetY() const
Definition: Track.cpp:150
bool mRefreshBacking
Definition: TrackPanel.h:487
Track * First(TrackList *val=NULL) override
Definition: Track.cpp:502
std::shared_ptr< TrackPanelCell > pCell
A kind of ToolBar with Tools on it.
Definition: ToolsToolBar.h:47
void ApplyUpdatedTheme()
Definition: TrackPanel.cpp:432
static void StatusDrawFunction(const wxString &string, wxDC *dc, const wxRect &rect)
void(*)(TrackPanelDrawingContext &context, const wxRect &rect, const Track *maybeNULL) DrawFunction
Track * GetFocusedTrack()
static void MidiControlsDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
void OnSetFocus(wxFocusEvent &event)
virtual bool TP_ScrollUpDown(int delta)=0
bool mRedrawAfterStop
Definition: TrackPanel.h:495
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:262
static std::unique_ptr< LWSlider > gPan
Definition: TrackPanel.h:244
float GetGain() const
Definition: WaveTrack.cpp:414
std::shared_ptr< TrackPanelCell > mpBackground
Definition: TrackPanel.h:559
AudacityProject * GetProject() const
Definition: TrackPanel.cpp:450
LWSlider * GainSlider(const WaveTrack *wt)
Definition: TrackPanel.cpp:383
This is an Audacity Specific ruler panel which additionally has border, selection markers...
Definition: Ruler.h:280
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1214
std::vector< UIHandlePtr > mTargets
Definition: TrackPanel.h:527
bool ChangeTarget(bool forward, bool cycle)
Definition: TrackPanel.cpp:991
void OnIdle(wxIdleEvent &event)
Definition: TrackPanel.cpp:470
int GetLeftOffset() const
Definition: TrackPanel.h:299
END_EVENT_TABLE()
const int kCaptureLostEventId
double GetRate() const
Definition: WaveTrack.cpp:397
void OnPaint(wxPaintEvent &event)
Definition: TrackPanel.cpp:574
std::unique_ptr< TrackPanelAx > mAx
Definition: TrackPanel.h:507
void UpdateVRulers()
static bool HasKeyboardCapture(const wxWindow *handler)
Definition: Project.cpp:5746
void OnKillFocus(wxFocusEvent &event)
static void GetSyncLockHorizontalBounds(const wxRect &rect, wxRect &dest)
void OnKeyDown(wxKeyEvent &event)
TrackInfo(TrackPanel *pParentIn)
static std::shared_ptr< Subclass > Pointer(Track *t)
Definition: Track.h:94
static void Status2DrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
wxMouseState mLastMouseState
Definition: TrackPanel.h:497
AdornedRulerPanel * mRuler
Definition: TrackPanel.h:465
void DrawBordersWithin(wxDC *dc, const wxRect &rect, const Track &track) const
std::weak_ptr< Track > mpClickedTrack
Definition: TrackPanel.h:556
void OnPlayback(wxCommandEvent &)
void MakeParentRedrawScrollbars()
Definition: TrackPanel.cpp:629
VisibleTrackIterator mIter
void HandleCursorForPresentMouseState(bool doHit=true)
Definition: TrackPanel.cpp:815
virtual void TP_DisplaySelection()=0
static TrackPanel * TrackPanelFactory(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, const std::shared_ptr< TrackList > &tracks, ViewInfo *viewInfo, TrackPanelListener *listener, AdornedRulerPanel *ruler)
wxSize vrulerSize
Definition: Track.h:133
void OnMouseEvent(wxMouseEvent &event)
static void GetPanRect(const wxPoint &topLeft, wxRect &dest)
static void SliderDrawFunction(LWSlider *(*Selector)(const wxRect &sliderRect, const TrackClass *t, bool captured, wxWindow *), wxDC *dc, const wxRect &rect, const Track *pTrack, bool captured, bool highlight)
void HandleModifierKey()
Definition: TrackPanel.cpp:800
virtual ToolsToolBar * TP_GetToolsToolBar()=0
static std::unique_ptr< LWSlider > gGain
Definition: TrackPanel.h:243
void SetLeftOffset(int offset)
Definition: Ruler.cpp:3203
static std::unique_ptr< LWSlider > gGainCaptured
Definition: TrackPanel.h:241
void HandleMotion(wxMouseState &state, bool doHit=true)
Definition: TrackPanel.cpp:844
static LWSlider * GainSlider(const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
int GetHeight() const
Definition: Track.cpp:160
int GetVRulerOffset() const
Definition: TrackPanel.h:393
static wxWindow * GetKeyboardCaptureHandler()
Definition: Project.cpp:5752
virtual unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent)
ViewInfo * GetViewInfo()
Definition: TrackPanel.h:402
A Track that is used for Midi notes. (Somewhat old code).
void UpdateVRulerSize()