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