From f6609e5ffbff1fed28a0aab5d291fadc34760659 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 4 Aug 2018 13:06:55 -0400 Subject: [PATCH] Move class CellularPanel into its own files --- mac/Audacity.xcodeproj/project.pbxproj | 6 + src/CellularPanel.cpp | 784 ++++++++++++++++++ src/CellularPanel.h | 148 ++++ src/Makefile.am | 2 + src/Makefile.in | 113 ++- src/TrackPanel.cpp | 750 ----------------- src/TrackPanel.h | 133 +-- win/Projects/Audacity/Audacity.vcxproj | 2 + .../Audacity/Audacity.vcxproj.filters | 6 + 9 files changed, 1023 insertions(+), 921 deletions(-) create mode 100644 src/CellularPanel.cpp create mode 100644 src/CellularPanel.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 63532e291..baf0e385e 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1206,6 +1206,7 @@ 5E07842E1DEE6B8600CA76EA /* FileException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842C1DEE6B8600CA76EA /* FileException.cpp */; }; 5E0784311DF1E4F400CA76EA /* UserException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842F1DF1E4F400CA76EA /* UserException.cpp */; }; 5E0A0E311D23019A00CD2567 /* MenusMac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E0A0E301D23019A00CD2567 /* MenusMac.cpp */; }; + 5E0A1CDD20E95FF7001AAF8D /* CellularPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E0A1CDB20E95FF7001AAF8D /* CellularPanel.cpp */; }; 5E10D9061EC8F81300B3AC57 /* PlayableTrackButtonHandles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E10D9041EC8F81300B3AC57 /* PlayableTrackButtonHandles.cpp */; }; 5E15123D1DB000C000702E29 /* UIHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E15123B1DB000C000702E29 /* UIHandle.cpp */; }; 5E15125A1DB000DC00702E29 /* LabelTrackControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E1512401DB000DC00702E29 /* LabelTrackControls.cpp */; }; @@ -3053,6 +3054,8 @@ 5E07842F1DF1E4F400CA76EA /* UserException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserException.cpp; sourceTree = ""; }; 5E0784301DF1E4F400CA76EA /* UserException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserException.h; sourceTree = ""; }; 5E0A0E301D23019A00CD2567 /* MenusMac.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = MenusMac.cpp; sourceTree = ""; }; + 5E0A1CDB20E95FF7001AAF8D /* CellularPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CellularPanel.cpp; sourceTree = ""; }; + 5E0A1CDC20E95FF7001AAF8D /* CellularPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CellularPanel.h; sourceTree = ""; }; 5E10D9041EC8F81300B3AC57 /* PlayableTrackButtonHandles.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlayableTrackButtonHandles.cpp; sourceTree = ""; }; 5E10D9051EC8F81300B3AC57 /* PlayableTrackButtonHandles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayableTrackButtonHandles.h; sourceTree = ""; }; 5E1512381DB000C000702E29 /* HitTestResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HitTestResult.h; sourceTree = ""; }; @@ -4053,6 +4056,7 @@ 1790AFD809883BFD008A330A /* BatchProcessDialog.cpp */, 1790AFDA09883BFD008A330A /* Benchmark.cpp */, 1790AFE809883BFD008A330A /* BlockFile.cpp */, + 5E0A1CDB20E95FF7001AAF8D /* CellularPanel.cpp */, 1790AFF409883BFD008A330A /* CrossFade.cpp */, 2849B4600A7444BE00ECF12D /* Dependencies.cpp */, 28D000A31A32920C00367B21 /* DeviceChange.cpp */, @@ -4147,6 +4151,7 @@ 1790AFD909883BFD008A330A /* BatchProcessDialog.h */, 1790AFDB09883BFD008A330A /* Benchmark.h */, 1790AFE909883BFD008A330A /* BlockFile.h */, + 5E0A1CDC20E95FF7001AAF8D /* CellularPanel.h */, 1790AFF009883BFD008A330A /* configtemplate.h */, 1790AFF509883BFD008A330A /* CrossFade.h */, 2849B4610A7444BE00ECF12D /* Dependencies.h */, @@ -7729,6 +7734,7 @@ 1790B11E09883BFD008A330A /* BatchCommands.cpp in Sources */, 5ED1D0B11CDE560C00471E3C /* BackedPanel.cpp in Sources */, 1790B11F09883BFD008A330A /* BatchProcessDialog.cpp in Sources */, + 5E0A1CDD20E95FF7001AAF8D /* CellularPanel.cpp in Sources */, 1790B12009883BFD008A330A /* Benchmark.cpp in Sources */, 1790B12109883BFD008A330A /* LegacyAliasBlockFile.cpp in Sources */, 1790B12209883BFD008A330A /* LegacyBlockFile.cpp in Sources */, diff --git a/src/CellularPanel.cpp b/src/CellularPanel.cpp new file mode 100644 index 000000000..16e513044 --- /dev/null +++ b/src/CellularPanel.cpp @@ -0,0 +1,784 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + CellularPanel.cpp + + Dominic Mazzoni + and lots of other contributors + + Implements CellularPanel. + +********************************************************************//*! + +\class CellularPanel +\brief + + Formerly part of TrackPanel, this abstract base class has no special knowledge + of Track objects and is intended for reuse with other windows. + + Manages a division of a panel's area into disjoint rectangles, each with + an associated Cell object. Details of that partition and association, and + the choice of the cell with keyboard focus, are subclass responsibilities. + + Handling of keyboard events is delegated to the focused cell. The cell under + the mouse position is queried for hit-test candidate objects, which handle + click-drag-release (and ESC key abort) sequences. + +*//*****************************************************************/ + +#include "Audacity.h" +#include "CellularPanel.h" +#include "Project.h" +#include "UIHandle.h" +#include "TrackPanelMouseEvent.h" +#include "HitTestResult.h" +#include "RefreshCode.h" + + +BEGIN_EVENT_TABLE(CellularPanel, OverlayPanel) + EVT_MOUSE_EVENTS(CellularPanel::OnMouseEvent) + EVT_MOUSE_CAPTURE_LOST(CellularPanel::OnCaptureLost) + EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, CellularPanel::OnCaptureKey) + EVT_KEY_DOWN(CellularPanel::OnKeyDown) + EVT_KEY_UP(CellularPanel::OnKeyUp) + EVT_CHAR(CellularPanel::OnChar) + EVT_SET_FOCUS(CellularPanel::OnSetFocus) + EVT_KILL_FOCUS(CellularPanel::OnKillFocus) + EVT_CONTEXT_MENU(CellularPanel::OnContextMenu) +END_EVENT_TABLE() + +void CellularPanel::HandleInterruptedDrag() +{ + if (mUIHandle && mUIHandle->StopsOnKeystroke() ) { + // The bogus id isn't used anywhere, but may help with debugging. + // as this is sending a bogus mouse up. The mouse button is still actually down + // and may go up again. + const int idBogusUp = 2; + wxMouseEvent evt { wxEVT_LEFT_UP }; + evt.SetId( idBogusUp ); + evt.SetPosition(this->ScreenToClient(::wxGetMousePosition())); + this->ProcessEvent(evt); + } +} + +void CellularPanel::Uncapture(wxMouseState *pState) +{ + auto state = ::wxGetMouseState(); + if (!pState) { + // Remap the position + state.SetPosition(this->ScreenToClient(state.GetPosition())); + pState = &state; + } + + if (HasCapture()) + ReleaseMouse(); + HandleMotion( *pState ); +} + +bool CellularPanel::CancelDragging() +{ + if (mUIHandle) { + // copy shared_ptr for safety, as in HandleClick + auto handle = mUIHandle; + // UIHANDLE CANCEL + UIHandle::Result refreshResult = handle->Cancel(GetProject()); + auto pClickedCell = mpClickedCell.lock(); + if (pClickedCell) + ProcessUIHandleResult( + pClickedCell.get(), {}, + refreshResult | mMouseOverUpdateFlags ); + mpClickedCell.reset(); + mUIHandle.reset(), handle.reset(), ClearTargets(); + Uncapture(); + return true; + } + return false; +} + +bool CellularPanel::HandleEscapeKey(bool down) +{ + if (!down) + return false; + + { + auto target = Target(); + if (target && target->HasEscape() && target->Escape()) { + HandleCursorForPresentMouseState(false); + return true; + } + } + + if (mUIHandle) { + CancelDragging(); + return true; + } + + if (ChangeTarget(true, false)) { + HandleCursorForPresentMouseState(false); + return true; + } + + return false; +} + +void CellularPanel::UpdateMouseState(const wxMouseState &state) +{ + mLastMouseState = state; + + // Simulate a down button if none, so hit test routines can anticipate + // which button will be clicked + if (!state.ButtonIsDown(wxMOUSE_BTN_ANY)) { +#ifdef __WXOSX__ + if (state.RawControlDown()) + // On Mac we can distinctly anticipate "right" click (as Control+click) + mLastMouseState.SetRightDown( true ), + mLastMouseState.SetLeftDown( false ); + else +#endif + // Anticipate a left click by default + mLastMouseState.SetRightDown( false ), + mLastMouseState.SetLeftDown( true ); + } +} + +void CellularPanel::HandleModifierKey() +{ + HandleCursorForPresentMouseState(); +} + +void CellularPanel::HandleCursorForPresentMouseState(bool doHit) +{ + // Come here on modifier key or mouse button transitions, + // or on starting or stopping of play or record, + // or change of toolbar button, + // and change the cursor appropriately. + + // Get the button and key states + auto state = ::wxGetMouseState(); + // Remap the position + state.SetPosition(this->ScreenToClient(state.GetPosition())); + + HandleMotion( state, doHit ); +} + +/// CellularPanel::HandleMotion( ) sets the cursor drawn at the mouse location, +/// and updates the status bar message. +/// We treat certain other changes of mouse button and key state as "motions" +/// too, and also starting and stopping of playback or recording, all of which +/// may cause the appropriate cursor and message to change. +/// As this procedure checks which region the mouse is over, it is +/// appropriate to establish the message in the status bar. +void CellularPanel::HandleMotion( wxMouseState &inState, bool doHit ) +{ + UpdateMouseState( inState ); + + const auto foundCell = FindCell( inState.m_x, inState.m_y ); + auto &rect = foundCell.rect; + auto &pCell = foundCell.pCell; + const TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell }; + HandleMotion( tpmState, doHit ); +} + +void CellularPanel::HandleMotion +( const TrackPanelMouseState &tpmState, bool doHit ) +{ + auto handle = mUIHandle; + + auto newCell = tpmState.pCell; + + wxString status{}, tooltip{}; + wxCursor *pCursor{}; + unsigned refreshCode = 0; + + if ( ! doHit ) { + // Dragging or not + handle = Target(); + + // Assume cell does not change but target does + refreshCode = mMouseOverUpdateFlags; + mMouseOverUpdateFlags = 0; + } + else if ( !mUIHandle ) { + // Not yet dragging. + + auto oldCell = mLastCell.lock(); + + unsigned updateFlags = mMouseOverUpdateFlags; + + // First check whether crossing cell to cell + if ( newCell == oldCell ) + oldCell.reset(); + else { + // Forget old targets + ClearTargets(); + // Re-draw any highlighting + if (oldCell) { + ProcessUIHandleResult( + oldCell.get(), oldCell.get(), updateFlags); + } + } + + auto oldHandle = Target(); + auto oldPosition = mTarget; + + // Now do the + // UIHANDLE HIT TEST ! + mTargets = newCell->HitTest(tpmState, GetProject()); + + mTarget = 0; + + // Find the old target's NEW place if we can + if (oldHandle) { + auto begin = mTargets.begin(), end = mTargets.end(), + iter = std::find(begin, end, oldHandle); + if (iter != end) { + size_t newPosition = iter - begin; + if (newPosition <= oldPosition) + mTarget = newPosition; + // else, some NEW hit and this position takes priority + } + } + + handle = Target(); + + mLastCell = newCell; + + if (!oldCell && oldHandle != handle) + // Did not move cell to cell, but did change the target + refreshCode = updateFlags; + + if (handle && handle != oldHandle) + handle->Enter(true); + } + + // UIHANDLE PREVIEW + // Update status message and cursor, whether dragging or not + if (handle) { + auto preview = handle->Preview( tpmState, GetProject() ); + status = preview.message; + tooltip = preview.tooltip; + pCursor = preview.cursor; + auto code = handle->GetChangeHighlight(); + handle->SetChangeHighlight(RefreshCode::RefreshNone); + refreshCode |= code; + mMouseOverUpdateFlags |= code; + } + if (newCell && + (!pCursor || status.empty() || tooltip.empty())) { + // Defaulting of cursor, tooltip, and status if there is no handle, + // or if the handle does not specify them + const auto preview = newCell->DefaultPreview( tpmState, GetProject() ); + if (!pCursor) + pCursor = preview.cursor; + if (status.empty()) + status = preview.message; + if (tooltip.empty()) + tooltip = preview.tooltip; + } + if (!pCursor) { + // Ultimate default cursor + static wxCursor defaultCursor{ wxCURSOR_DEFAULT }; + pCursor = &defaultCursor; + } + + UpdateStatusMessage(status); + +#if wxUSE_TOOLTIPS + if (tooltip != GetToolTipText()) { + // Unset first, by analogy with AButton + UnsetToolTip(); + SetToolTip(tooltip); + } +#endif + + if (pCursor) + SetCursor( *pCursor ); + + ProcessUIHandleResult( + newCell.get(), newCell.get(), refreshCode); +} + +bool CellularPanel::HasRotation() +{ + // Is there a nontrivial TAB key rotation? + if ( mTargets.size() > 1 ) + return true; + auto target = Target(); + return target && target->HasRotation(); +} + +bool CellularPanel::HasEscape() +{ + if (IsMouseCaptured()) + return true; + + if (mTarget + 1 == mTargets.size() && + Target() && + !Target()->HasEscape()) + return false; + + return mTargets.size() > 0; +} + +bool CellularPanel::ChangeTarget(bool forward, bool cycle) +{ + auto size = mTargets.size(); + + auto target = Target(); + if (target && target->HasRotation()) { + if(target->Rotate(forward)) + return true; + else if (cycle && (size == 1 || IsMouseCaptured())) { + // Rotate through the states of this target only. + target->Enter(forward); + return true; + } + } + + if (!cycle && + ((forward && mTarget + 1 == size) || + (!forward && mTarget == 0))) + return false; + + if (size > 1) { + if (forward) + ++mTarget; + else + mTarget += size - 1; + mTarget %= size; + if (Target()) + Target()->Enter(forward); + return true; + } + + return false; +} + +/// Determines if a modal tool is active +bool CellularPanel::IsMouseCaptured() +{ + return mUIHandle != NULL; +} + +void CellularPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event)) +{ + DoContextMenu(); +} + +/// Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling) +void CellularPanel::HandleWheelRotation( TrackPanelMouseEvent &tpmEvent ) +{ + auto pCell = tpmEvent.pCell; + if (!pCell) + return; + + auto &event = tpmEvent.event; + double steps {}; +#if defined(__WXMAC__) && defined(EVT_MAGNIFY) + // PRL: + // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from + // the future in custom build of 3.0.2 + if (event.Magnify()) { + event.SetControlDown(true); + steps = 2 * event.GetMagnification(); + } + else +#endif + { + steps = event.m_wheelRotation / + (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0); + } + + if(event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL) { + // Two-fingered horizontal swipe on mac is treated like shift-mousewheel + event.SetShiftDown(true); + // This makes the wave move in the same direction as the fingers, and the scrollbar + // thumb moves oppositely + steps *= -1; + } + + tpmEvent.steps = steps; + + if(!event.HasAnyModifiers()) { + // We will later un-skip if we do anything, but if we don't, + // propagate the event up for the sake of the scrubber + event.Skip(); + event.ResumePropagation(wxEVENT_PROPAGATE_MAX); + } + + unsigned result = + pCell->HandleWheelRotation( tpmEvent, GetProject() ); + ProcessUIHandleResult( + pCell.get(), pCell.get(), result); +} + +void CellularPanel::OnCaptureKey(wxCommandEvent & event) +{ + mEnableTab = false; + wxKeyEvent *kevent = static_cast(event.GetEventObject()); + const auto code = kevent->GetKeyCode(); + if ( WXK_ESCAPE != code ) + HandleInterruptedDrag(); + + // Give focused cell precedence + const auto t = GetFocusedCell(); + if (t) { + const unsigned refreshResult = + t->CaptureKey(*kevent, *mViewInfo, this); + ProcessUIHandleResult(t, t, refreshResult); + event.Skip(kevent->GetSkipped()); + } + +#if 0 + // Special TAB key handling, but only if the cell didn't capture it + if ( !(t && !kevent->GetSkipped()) && + WXK_TAB == code && HasRotation() ) { + // Override TAB navigation in wxWidgets, by not skipping + event.Skip(false); + mEnableTab = true; + return; + } + else +#endif + if (!t) + event.Skip(); +} + +void CellularPanel::OnKeyDown(wxKeyEvent & event) +{ + switch (event.GetKeyCode()) + { + case WXK_ESCAPE: + if(HandleEscapeKey(true)) + // Don't skip the event, eat it so that + // AudacityApp does not also stop any playback. + return; + else + break; + + case WXK_ALT: + case WXK_SHIFT: + case WXK_CONTROL: +#ifdef __WXOSX__ + case WXK_RAW_CONTROL: +#endif + HandleModifierKey(); + break; + +#if 0 + case WXK_TAB: + if ( mEnableTab && HasRotation() ) { + ChangeTarget( !event.ShiftDown(), true ); + HandleCursorForPresentMouseState(false); + return; + } + else + break; +#endif + } + + const auto t = GetFocusedCell(); + + if (t) { + const unsigned refreshResult = + t->KeyDown(event, *mViewInfo, this); + ProcessUIHandleResult(t, t, refreshResult); + } + else + event.Skip(); +} + +void CellularPanel::OnChar(wxKeyEvent & event) +{ + switch (event.GetKeyCode()) + { + case WXK_ESCAPE: + case WXK_ALT: + case WXK_SHIFT: + case WXK_CONTROL: + case WXK_PAGEUP: + case WXK_PAGEDOWN: + return; + } + + const auto t = GetFocusedCell(); + if (t) { + const unsigned refreshResult = + t->Char(event, *mViewInfo, this); + ProcessUIHandleResult(t, t, refreshResult); + } + else + event.Skip(); +} + +void CellularPanel::OnKeyUp(wxKeyEvent & event) +{ + bool didSomething = false; + switch (event.GetKeyCode()) + { + case WXK_ESCAPE: + didSomething = HandleEscapeKey(false); + break; + + case WXK_ALT: + case WXK_SHIFT: + case WXK_CONTROL: +#ifdef __WXOSX__ + case WXK_RAW_CONTROL: +#endif + HandleModifierKey(); + break; + } + + if (didSomething) + return; + + const auto t = GetFocusedCell(); + if (t) { + const unsigned refreshResult = + t->KeyUp(event, *mViewInfo, this); + ProcessUIHandleResult(t, t, refreshResult); + return; + } + + event.Skip(); +} + +/// Should handle the case when the mouse capture is lost. +void CellularPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) +{ + ClearTargets(); + + // This is bad. We are lying abou the event by saying it is a mouse up. + wxMouseEvent e(wxEVT_LEFT_UP); + e.SetId( kCaptureLostEventId ); + + e.m_x = mMouseMostRecentX; + e.m_y = mMouseMostRecentY; + + OnMouseEvent(e); +} + +/// This handles just generic mouse events. Then, based +/// on our current state, we forward the mouse events to +/// various interested parties. +void CellularPanel::OnMouseEvent(wxMouseEvent & event) +try +{ + const auto foundCell = FindCell( event.m_x, event.m_y ); + auto &rect = foundCell.rect; + auto &pCell = foundCell.pCell; + + const auto size = GetSize(); + TrackPanelMouseEvent tpmEvent{ event, rect, size, pCell }; + +#if defined(__WXMAC__) && defined(EVT_MAGNIFY) + // PRL: + // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from + // the future in custom build of 3.0.2 + if (event.Magnify()) { + HandleWheelRotation( tpmEvent ); + } +#endif + + // If a mouse event originates from a keyboard context menu event then + // event.GetPosition() == wxDefaultPosition. wxContextMenu events are handled in + // CellularPanel::OnContextMenu(), and therefore associated mouse events are ignored here. + // Not ignoring them was causing bug 613: the mouse events were interpreted as clicking + // outside the tracks. + if (event.GetPosition() == wxDefaultPosition && (event.RightDown() || event.RightUp())) { + event.Skip(); + return; + } + + if (event.m_wheelRotation != 0) + HandleWheelRotation( tpmEvent ); + + if (event.LeftDown() || event.LeftIsDown() || event.Moving()) { + // Skip, even if we do something, so that the left click or drag + // may have an additional effect in the scrubber. + event.Skip(); + event.ResumePropagation(wxEVENT_PROPAGATE_MAX); + } + + mMouseMostRecentX = event.m_x; + mMouseMostRecentY = event.m_y; + + if (event.LeftDown()) { + // The activate event is used to make the + // parent window 'come alive' if it didn't have focus. + wxActivateEvent e; + GetParent()->GetEventHandler()->ProcessEvent(e); + } + + if (event.ButtonDown()) { + SetFocus(); + } + + if (event.Leaving()) + { + if ( !mUIHandle ) + ClearTargets(); + + auto buttons = + // Bug 1325: button state in Leaving events is unreliable on Mac. + // Poll the global state instead. + // event.ButtonIsDown(wxMOUSE_BTN_ANY); + ::wxGetMouseState().ButtonIsDown(wxMOUSE_BTN_ANY); + + if(!buttons) { + CancelDragging(); + +#if defined(__WXMAC__) + + // We must install the cursor ourselves since the window under + // the mouse is no longer this one and wx2.8.12 makes that check. + // Should re-evaluate with wx3. + wxSTANDARD_CURSOR->MacInstall(); +#endif + } + } + + if (mUIHandle) { + auto pClickedCell = mpClickedCell.lock(); + if (event.Dragging()) { + // UIHANDLE DRAG + // copy shared_ptr for safety, as in HandleClick + auto handle = mUIHandle; + const UIHandle::Result refreshResult = + handle->Drag( tpmEvent, GetProject() ); + ProcessUIHandleResult + (pClickedCell.get(), pCell.get(), refreshResult); + mMouseOverUpdateFlags |= refreshResult; + if (refreshResult & RefreshCode::Cancelled) { + // Drag decided to abort itself + mUIHandle.reset(), handle.reset(), ClearTargets(); + mpClickedCell.reset(); + Uncapture( &event ); + } + else { + UpdateMouseState(event); + TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell }; + HandleMotion( tpmState ); + } + } + else if (event.ButtonUp()) { + // UIHANDLE RELEASE + unsigned moreFlags = mMouseOverUpdateFlags; + UIHandle::Result refreshResult = + mUIHandle->Release( tpmEvent, GetProject(), this ); + ProcessUIHandleResult + (pClickedCell.get(), pCell.get(), + refreshResult | moreFlags); + mUIHandle.reset(), ClearTargets(); + mpClickedCell.reset(); + // will also Uncapture() below + } + } + else if ( event.GetEventType() == wxEVT_MOTION ) + // Update status message and cursor, not during drag + // consider it not a drag, even if button is down during motion, if + // mUIHandle is null, as it becomes during interrupted drag + // (e.g. by hitting space to play while dragging an envelope point) + HandleMotion( event ); + else if ( event.ButtonDown() || event.ButtonDClick() ) + HandleClick( tpmEvent ); + + if (event.ButtonDown() && IsMouseCaptured()) { + if (!HasCapture()) + CaptureMouse(); + } + + if (event.ButtonUp()) + Uncapture(); +} +catch( ... ) +{ + // Abort any dragging, as if by hitting Esc + if ( CancelDragging() ) + ; + else { + Uncapture(); + Refresh(false); + } + throw; +} + +void CellularPanel::HandleClick( const TrackPanelMouseEvent &tpmEvent ) +{ + auto pCell = tpmEvent.pCell; + + // Do hit test once more, in case the button really pressed was not the + // one "anticipated." + { + TrackPanelMouseState tpmState{ + tpmEvent.event, + tpmEvent.rect, + tpmEvent.pCell + }; + HandleMotion( tpmState ); + } + + mUIHandle = Target(); + + if (mUIHandle) { + // UIHANDLE CLICK + // Make another shared pointer to the handle, in case recursive + // event dispatching otherwise tries to delete the handle. + auto handle = mUIHandle; + UIHandle::Result refreshResult = + handle->Click( tpmEvent, GetProject() ); + if (refreshResult & RefreshCode::Cancelled) + mUIHandle.reset(), handle.reset(), ClearTargets(); + else { + mpClickedCell = pCell; + + // Perhaps the clicked handle wants to update cursor and state message + // after a click. + TrackPanelMouseState tpmState{ + tpmEvent.event, + tpmEvent.rect, + tpmEvent.pCell + }; + HandleMotion( tpmState ); + } + ProcessUIHandleResult( + pCell.get(), pCell.get(), refreshResult); + mMouseOverUpdateFlags |= refreshResult; + } +} + +void CellularPanel::DoContextMenu( TrackPanelCell *pCell ) +{ + if( !pCell ) { + pCell = GetFocusedCell(); + if( !pCell ) + return; + } + + const auto delegate = pCell->ContextMenuDelegate(); + if (!delegate) + return; + + auto rect = FindRect( *delegate ); + const UIHandle::Result refreshResult = + delegate->DoContextMenu(rect, this, NULL); + + // To do: use safer shared_ptr to pCell + ProcessUIHandleResult(pCell, pCell, refreshResult); +} + +void CellularPanel::OnSetFocus(wxFocusEvent & WXUNUSED(event)) +{ + SetFocusedCell(); +} + +void CellularPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event)) +{ + if (AudacityProject::HasKeyboardCapture(this)) + { + AudacityProject::ReleaseKeyboard(this); + } + Refresh( false); +} diff --git a/src/CellularPanel.h b/src/CellularPanel.h new file mode 100644 index 000000000..a576b823e --- /dev/null +++ b/src/CellularPanel.h @@ -0,0 +1,148 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + TrackPanel.h + + Paul Licameli + + **********************************************************************/ + +#ifndef __AUDACITY_CELLULAR_PANEL__ +#define __AUDACITY_CELLULAR_PANEL__ + +#include +#include "widgets/OverlayPanel.h" + +class ViewInfo; +class AudacityProject; + +class TrackPanelCell; +struct TrackPanelMouseEvent; +struct TrackPanelMouseState; + +class UIHandle; +using UIHandlePtr = std::shared_ptr; + +// This class manages a panel divided into a number of sub-rectangles called +// cells, that each implement hit tests returning click-drag-release handler +// objects, and other services. +// It has no dependency on the Track class. +class AUDACITY_DLL_API CellularPanel : public OverlayPanel { +public: + CellularPanel(wxWindow * parent, wxWindowID id, + const wxPoint & pos, + const wxSize & size, + ViewInfo *viewInfo, + // default as for wxPanel: + long style = wxTAB_TRAVERSAL | wxNO_BORDER) + : OverlayPanel(parent, id, pos, size, style) + , mViewInfo( viewInfo ) + {} + + // Overridables: + + virtual AudacityProject *GetProject() const = 0; + + // Find track info by coordinate + struct FoundCell { + std::shared_ptr pCell; + wxRect rect; + }; + virtual FoundCell FindCell(int mouseX, int mouseY) = 0; + virtual wxRect FindRect(const TrackPanelCell &cell) = 0; + virtual TrackPanelCell *GetFocusedCell() = 0; + virtual void SetFocusedCell() = 0; + + virtual void ProcessUIHandleResult + (TrackPanelCell *pClickedCell, TrackPanelCell *pLatestCell, + unsigned refreshResult) = 0; + + virtual void UpdateStatusMessage( const wxString & ) = 0; + +public: + UIHandlePtr Target() + { + if (mTargets.size()) + return mTargets[mTarget]; + else + return {}; + } + + bool IsMouseCaptured(); + + wxCoord MostRecentXCoord() const { return mMouseMostRecentX; } + + void HandleCursorForPresentMouseState(bool doHit = true); + +protected: + bool HasEscape(); + bool CancelDragging(); + void DoContextMenu( TrackPanelCell *pCell = nullptr ); + void ClearTargets() + { + // Forget the rotation of hit test candidates when the mouse moves from + // cell to cell or outside of the panel entirely. + mLastCell.reset(); + mTargets.clear(); + mTarget = 0; + mMouseOverUpdateFlags = 0; + } + +private: + bool HasRotation(); + bool ChangeTarget(bool forward, bool cycle); + + void OnMouseEvent(wxMouseEvent & event); + void OnCaptureLost(wxMouseCaptureLostEvent & event); + void OnCaptureKey(wxCommandEvent & event); + void OnKeyDown(wxKeyEvent & event); + void OnChar(wxKeyEvent & event); + void OnKeyUp(wxKeyEvent & event); + + void OnSetFocus(wxFocusEvent & event); + void OnKillFocus(wxFocusEvent & event); + + void OnContextMenu(wxContextMenuEvent & event); + + void HandleInterruptedDrag(); + void Uncapture( wxMouseState *pState = nullptr ); + bool HandleEscapeKey(bool down); + void UpdateMouseState(const wxMouseState &state); + void HandleModifierKey(); + + void HandleClick( const TrackPanelMouseEvent &tpmEvent ); + void HandleWheelRotation( TrackPanelMouseEvent &tpmEvent ); + + void HandleMotion( wxMouseState &state, bool doHit = true ); + void HandleMotion + ( const TrackPanelMouseState &tpmState, bool doHit = true ); + + +protected: + ViewInfo *mViewInfo; + +private: + UIHandlePtr mUIHandle; + + std::weak_ptr mLastCell; + std::vector mTargets; + size_t mTarget {}; + unsigned mMouseOverUpdateFlags{}; + +protected: + // To do: make a drawing method and make this private + wxMouseState mLastMouseState; + +private: + int mMouseMostRecentX; + int mMouseMostRecentY; + + std::weak_ptr mpClickedCell; + + bool mEnableTab{}; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index e61be0e4e..37d567cab 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -121,6 +121,8 @@ audacity_SOURCES = \ BatchProcessDialog.h \ Benchmark.cpp \ Benchmark.h \ + CellularPanel.cpp \ + CellularPanel.h \ Dependencies.cpp \ Dependencies.h \ DeviceChange.cpp \ diff --git a/src/Makefile.in b/src/Makefile.in index d28e7abc1..501bf8237 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.14.1 from Makefile.am. +# Makefile.in generated by automake 1.15 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2013 Free Software Foundation, Inc. +# Copyright (C) 1994-2014 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -16,7 +16,17 @@ VPATH = @srcdir@ -am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ @@ -190,9 +200,6 @@ bin_PROGRAMS = audacity$(EXEEXT) @USE_VST_TRUE@ $(NULL) subdir = src -DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ - $(srcdir)/configtemplate.h $(srcdir)/audacity.desktop.in \ - $(top_srcdir)/autotools/depcomp $(dist_mime_DATA) ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_c99_func_lrint.m4 \ $(top_srcdir)/m4/ac_c99_func_lrintf.m4 \ @@ -235,6 +242,8 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ac_c99_func_lrint.m4 \ $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dist_mime_DATA) \ + $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = configwin.h configunix.h CONFIG_CLEAN_FILES = audacity.desktop @@ -287,22 +296,23 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ AutoRecovery.h BatchCommandDialog.cpp BatchCommandDialog.h \ BatchCommands.cpp BatchCommands.h BatchProcessDialog.cpp \ BatchProcessDialog.h Benchmark.cpp Benchmark.h \ - Dependencies.cpp Dependencies.h DeviceChange.cpp \ - DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \ - Diags.h Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp \ - FFmpeg.h FFT.cpp FFT.h FileException.cpp FileException.h \ - FileIO.cpp FileIO.h FileNames.cpp FileNames.h float_cast.h \ - FreqWindow.cpp FreqWindow.h HelpText.cpp HelpText.h \ - HistoryWindow.cpp HistoryWindow.h HitTestResult.h \ - ImageManipulation.cpp ImageManipulation.h \ - InconsistencyException.cpp InconsistencyException.h \ - InterpolateAudio.cpp InterpolateAudio.h LabelDialog.cpp \ - LabelDialog.h LabelTrack.cpp LabelTrack.h LangChoice.cpp \ - LangChoice.h Languages.cpp Languages.h Legacy.cpp Legacy.h \ - Lyrics.cpp Lyrics.h LyricsWindow.cpp LyricsWindow.h \ - MacroMagic.h Matrix.cpp Matrix.h MemoryX.h Menus.cpp Menus.h \ - Mix.cpp Mix.h MixerBoard.cpp MixerBoard.h ModuleManager.cpp \ - ModuleManager.h NumberScale.h PitchName.cpp PitchName.h \ + CellularPanel.cpp CellularPanel.h Dependencies.cpp \ + Dependencies.h DeviceChange.cpp DeviceChange.h \ + DeviceManager.cpp DeviceManager.h Diags.cpp Diags.h \ + Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp FFmpeg.h \ + FFT.cpp FFT.h FileException.cpp FileException.h FileIO.cpp \ + FileIO.h FileNames.cpp FileNames.h float_cast.h FreqWindow.cpp \ + FreqWindow.h HelpText.cpp HelpText.h HistoryWindow.cpp \ + HistoryWindow.h HitTestResult.h ImageManipulation.cpp \ + ImageManipulation.h InconsistencyException.cpp \ + InconsistencyException.h InterpolateAudio.cpp \ + InterpolateAudio.h LabelDialog.cpp LabelDialog.h \ + LabelTrack.cpp LabelTrack.h LangChoice.cpp LangChoice.h \ + Languages.cpp Languages.h Legacy.cpp Legacy.h Lyrics.cpp \ + Lyrics.h LyricsWindow.cpp LyricsWindow.h MacroMagic.h \ + Matrix.cpp Matrix.h MemoryX.h Menus.cpp Menus.h Mix.cpp Mix.h \ + MixerBoard.cpp MixerBoard.h ModuleManager.cpp ModuleManager.h \ + NumberScale.h PitchName.cpp PitchName.h \ PlatformCompatibility.cpp PlatformCompatibility.h \ PluginManager.cpp PluginManager.h Printing.cpp Printing.h \ Profiler.cpp Profiler.h Project.cpp Project.h RealFFTf.cpp \ @@ -612,7 +622,8 @@ am_audacity_OBJECTS = $(am__objects_1) audacity-AboutDialog.$(OBJEXT) \ audacity-BatchCommandDialog.$(OBJEXT) \ audacity-BatchCommands.$(OBJEXT) \ audacity-BatchProcessDialog.$(OBJEXT) \ - audacity-Benchmark.$(OBJEXT) audacity-Dependencies.$(OBJEXT) \ + audacity-Benchmark.$(OBJEXT) audacity-CellularPanel.$(OBJEXT) \ + audacity-Dependencies.$(OBJEXT) \ audacity-DeviceChange.$(OBJEXT) \ audacity-DeviceManager.$(OBJEXT) audacity-Diags.$(OBJEXT) \ audacity-Envelope.$(OBJEXT) audacity-FFmpeg.$(OBJEXT) \ @@ -1008,6 +1019,8 @@ am__define_uniq_tagged_files = \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/audacity.desktop.in \ + $(srcdir)/configtemplate.h $(top_srcdir)/autotools/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -1102,6 +1115,7 @@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBINTL = @LTLIBINTL@ LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LV2_CFLAGS = @LV2_CFLAGS@ LV2_LIBS = @LV2_LIBS@ MAINT = @MAINT@ @@ -1127,6 +1141,8 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ PORTAUDIO_CFLAGS = @PORTAUDIO_CFLAGS@ PORTAUDIO_LIBS = @PORTAUDIO_LIBS@ PORTMIDI_CFLAGS = @PORTMIDI_CFLAGS@ @@ -1308,22 +1324,23 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \ AutoRecovery.h BatchCommandDialog.cpp BatchCommandDialog.h \ BatchCommands.cpp BatchCommands.h BatchProcessDialog.cpp \ BatchProcessDialog.h Benchmark.cpp Benchmark.h \ - Dependencies.cpp Dependencies.h DeviceChange.cpp \ - DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp \ - Diags.h Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp \ - FFmpeg.h FFT.cpp FFT.h FileException.cpp FileException.h \ - FileIO.cpp FileIO.h FileNames.cpp FileNames.h float_cast.h \ - FreqWindow.cpp FreqWindow.h HelpText.cpp HelpText.h \ - HistoryWindow.cpp HistoryWindow.h HitTestResult.h \ - ImageManipulation.cpp ImageManipulation.h \ - InconsistencyException.cpp InconsistencyException.h \ - InterpolateAudio.cpp InterpolateAudio.h LabelDialog.cpp \ - LabelDialog.h LabelTrack.cpp LabelTrack.h LangChoice.cpp \ - LangChoice.h Languages.cpp Languages.h Legacy.cpp Legacy.h \ - Lyrics.cpp Lyrics.h LyricsWindow.cpp LyricsWindow.h \ - MacroMagic.h Matrix.cpp Matrix.h MemoryX.h Menus.cpp Menus.h \ - Mix.cpp Mix.h MixerBoard.cpp MixerBoard.h ModuleManager.cpp \ - ModuleManager.h NumberScale.h PitchName.cpp PitchName.h \ + CellularPanel.cpp CellularPanel.h Dependencies.cpp \ + Dependencies.h DeviceChange.cpp DeviceChange.h \ + DeviceManager.cpp DeviceManager.h Diags.cpp Diags.h \ + Envelope.cpp Envelope.h Experimental.h FFmpeg.cpp FFmpeg.h \ + FFT.cpp FFT.h FileException.cpp FileException.h FileIO.cpp \ + FileIO.h FileNames.cpp FileNames.h float_cast.h FreqWindow.cpp \ + FreqWindow.h HelpText.cpp HelpText.h HistoryWindow.cpp \ + HistoryWindow.h HitTestResult.h ImageManipulation.cpp \ + ImageManipulation.h InconsistencyException.cpp \ + InconsistencyException.h InterpolateAudio.cpp \ + InterpolateAudio.h LabelDialog.cpp LabelDialog.h \ + LabelTrack.cpp LabelTrack.h LangChoice.cpp LangChoice.h \ + Languages.cpp Languages.h Legacy.cpp Legacy.h Lyrics.cpp \ + Lyrics.h LyricsWindow.cpp LyricsWindow.h MacroMagic.h \ + Matrix.cpp Matrix.h MemoryX.h Menus.cpp Menus.h Mix.cpp Mix.h \ + MixerBoard.cpp MixerBoard.h ModuleManager.cpp ModuleManager.h \ + NumberScale.h PitchName.cpp PitchName.h \ PlatformCompatibility.cpp PlatformCompatibility.h \ PluginManager.cpp PluginManager.h Printing.cpp Printing.h \ Profiler.cpp Profiler.h Project.cpp Project.h RealFFTf.cpp \ @@ -1602,7 +1619,6 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/Makefile -.PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -2408,6 +2424,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-BatchProcessDialog.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-Benchmark.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-BlockFile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-CellularPanel.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-Dependencies.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-DeviceChange.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audacity-DeviceManager.Po@am__quote@ @@ -3273,6 +3290,20 @@ audacity-Benchmark.obj: Benchmark.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o audacity-Benchmark.obj `if test -f 'Benchmark.cpp'; then $(CYGPATH_W) 'Benchmark.cpp'; else $(CYGPATH_W) '$(srcdir)/Benchmark.cpp'; fi` +audacity-CellularPanel.o: CellularPanel.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT audacity-CellularPanel.o -MD -MP -MF $(DEPDIR)/audacity-CellularPanel.Tpo -c -o audacity-CellularPanel.o `test -f 'CellularPanel.cpp' || echo '$(srcdir)/'`CellularPanel.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/audacity-CellularPanel.Tpo $(DEPDIR)/audacity-CellularPanel.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='CellularPanel.cpp' object='audacity-CellularPanel.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o audacity-CellularPanel.o `test -f 'CellularPanel.cpp' || echo '$(srcdir)/'`CellularPanel.cpp + +audacity-CellularPanel.obj: CellularPanel.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT audacity-CellularPanel.obj -MD -MP -MF $(DEPDIR)/audacity-CellularPanel.Tpo -c -o audacity-CellularPanel.obj `if test -f 'CellularPanel.cpp'; then $(CYGPATH_W) 'CellularPanel.cpp'; else $(CYGPATH_W) '$(srcdir)/CellularPanel.cpp'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/audacity-CellularPanel.Tpo $(DEPDIR)/audacity-CellularPanel.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='CellularPanel.cpp' object='audacity-CellularPanel.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o audacity-CellularPanel.obj `if test -f 'CellularPanel.cpp'; then $(CYGPATH_W) 'CellularPanel.cpp'; else $(CYGPATH_W) '$(srcdir)/CellularPanel.cpp'; fi` + audacity-Dependencies.o: Dependencies.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT audacity-Dependencies.o -MD -MP -MF $(DEPDIR)/audacity-Dependencies.Tpo -c -o audacity-Dependencies.o `test -f 'Dependencies.cpp' || echo '$(srcdir)/'`Dependencies.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/audacity-Dependencies.Tpo $(DEPDIR)/audacity-Dependencies.Po @@ -7722,6 +7753,8 @@ uninstall-am: uninstall-binPROGRAMS uninstall-desktopDATA \ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ uninstall-desktopDATA uninstall-dist_mimeDATA +.PRECIOUS: Makefile + # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index dc75ae388..7118d7f4e 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -169,8 +169,6 @@ is time to refresh some aspect of the screen. #include "RefreshCode.h" #include "TrackArtist.h" #include "TrackPanelAx.h" -#include "UIHandle.h" -#include "HitTestResult.h" #include "WaveTrack.h" #ifdef EXPERIMENTAL_MIDI_OUT #include "NoteTrack.h" @@ -254,18 +252,6 @@ template < class A, class B, class DIST > bool within(A a, B b, DIST d) return (a > b - d) && (a < b + d); } -BEGIN_EVENT_TABLE(CellularPanel, OverlayPanel) - EVT_MOUSE_EVENTS(CellularPanel::OnMouseEvent) - EVT_MOUSE_CAPTURE_LOST(CellularPanel::OnCaptureLost) - EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, CellularPanel::OnCaptureKey) - EVT_KEY_DOWN(CellularPanel::OnKeyDown) - EVT_KEY_UP(CellularPanel::OnKeyUp) - EVT_CHAR(CellularPanel::OnChar) - EVT_SET_FOCUS(CellularPanel::OnSetFocus) - EVT_KILL_FOCUS(CellularPanel::OnKillFocus) - EVT_CONTEXT_MENU(CellularPanel::OnContextMenu) -END_EVENT_TABLE() - BEGIN_EVENT_TABLE(TrackPanel, CellularPanel) EVT_MOUSE_EVENTS(TrackPanel::OnMouseEvent) EVT_KEY_DOWN(TrackPanel::OnKeyDown) @@ -620,20 +606,6 @@ void TrackPanel::MakeParentRedrawScrollbars() mListener->TP_RedrawScrollbars(); } -void CellularPanel::HandleInterruptedDrag() -{ - if (mUIHandle && mUIHandle->StopsOnKeystroke() ) { - // The bogus id isn't used anywhere, but may help with debugging. - // as this is sending a bogus mouse up. The mouse button is still actually down - // and may go up again. - const int idBogusUp = 2; - wxMouseEvent evt { wxEVT_LEFT_UP }; - evt.SetId( idBogusUp ); - evt.SetPosition(this->ScreenToClient(::wxGetMousePosition())); - this->ProcessEvent(evt); - } -} - namespace { std::shared_ptr FindTrack(TrackPanelCell *pCell ) { @@ -724,91 +696,6 @@ void TrackPanel::ProcessUIHandleResult panel->EnsureVisible(pClickedTrack); } -void CellularPanel::Uncapture(wxMouseState *pState) -{ - auto state = ::wxGetMouseState(); - if (!pState) { - // Remap the position - state.SetPosition(this->ScreenToClient(state.GetPosition())); - pState = &state; - } - - if (HasCapture()) - ReleaseMouse(); - HandleMotion( *pState ); -} - -bool CellularPanel::CancelDragging() -{ - if (mUIHandle) { - // copy shared_ptr for safety, as in HandleClick - auto handle = mUIHandle; - // UIHANDLE CANCEL - UIHandle::Result refreshResult = handle->Cancel(GetProject()); - auto pClickedCell = mpClickedCell.lock(); - if (pClickedCell) - ProcessUIHandleResult( - pClickedCell.get(), {}, - refreshResult | mMouseOverUpdateFlags ); - mpClickedCell.reset(); - mUIHandle.reset(), handle.reset(), ClearTargets(); - Uncapture(); - return true; - } - return false; -} - -bool CellularPanel::HandleEscapeKey(bool down) -{ - if (!down) - return false; - - { - auto target = Target(); - if (target && target->HasEscape() && target->Escape()) { - HandleCursorForPresentMouseState(false); - return true; - } - } - - if (mUIHandle) { - CancelDragging(); - return true; - } - - if (ChangeTarget(true, false)) { - HandleCursorForPresentMouseState(false); - return true; - } - - return false; -} - -void CellularPanel::UpdateMouseState(const wxMouseState &state) -{ - mLastMouseState = state; - - // Simulate a down button if none, so hit test routines can anticipate - // which button will be clicked - if (!state.ButtonIsDown(wxMOUSE_BTN_ANY)) { -#ifdef __WXOSX__ - if (state.RawControlDown()) - // On Mac we can distinctly anticipate "right" click (as Control+click) - mLastMouseState.SetRightDown( true ), - mLastMouseState.SetLeftDown( false ); - else -#endif - // Anticipate a left click by default - mLastMouseState.SetRightDown( false ), - mLastMouseState.SetLeftDown( true ); - } -} - -void CellularPanel::HandleModifierKey() -{ - HandleCursorForPresentMouseState(); -} - void TrackPanel::HandlePageUpKey() { mListener->TP_ScrollWindow(2 * mViewInfo->h - GetScreenEndTime()); @@ -819,165 +706,12 @@ void TrackPanel::HandlePageDownKey() mListener->TP_ScrollWindow(GetScreenEndTime()); } -void CellularPanel::HandleCursorForPresentMouseState(bool doHit) -{ - // Come here on modifier key or mouse button transitions, - // or on starting or stopping of play or record, - // or change of toolbar button, - // and change the cursor appropriately. - - // Get the button and key states - auto state = ::wxGetMouseState(); - // Remap the position - state.SetPosition(this->ScreenToClient(state.GetPosition())); - - HandleMotion( state, doHit ); -} - bool TrackPanel::IsAudioActive() { AudacityProject *p = GetProject(); return p->IsAudioActive(); } - -/// TrackPanel::HandleMotion( ) sets the cursor drawn at the mouse location, -/// and updates the status bar message. -/// We treat certain other changes of mouse button and key state as "motions" -/// too, and also starting and stopping of playback or recording, all of which -/// may cause the appropriate cursor and message to change. -/// As this procedure checks which region the mouse is over, it is -/// appropriate to establish the message in the status bar. -void CellularPanel::HandleMotion( wxMouseState &inState, bool doHit ) -{ - UpdateMouseState( inState ); - - const auto foundCell = FindCell( inState.m_x, inState.m_y ); - auto &rect = foundCell.rect; - auto &pCell = foundCell.pCell; - const TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell }; - HandleMotion( tpmState, doHit ); -} - -void CellularPanel::HandleMotion -( const TrackPanelMouseState &tpmState, bool doHit ) -{ - auto handle = mUIHandle; - - auto newCell = tpmState.pCell; - - wxString status{}, tooltip{}; - wxCursor *pCursor{}; - unsigned refreshCode = 0; - - if ( ! doHit ) { - // Dragging or not - handle = Target(); - - // Assume cell does not change but target does - refreshCode = mMouseOverUpdateFlags; - mMouseOverUpdateFlags = 0; - } - else if ( !mUIHandle ) { - // Not yet dragging. - - auto oldCell = mLastCell.lock(); - - unsigned updateFlags = mMouseOverUpdateFlags; - - // First check whether crossing cell to cell - if ( newCell == oldCell ) - oldCell.reset(); - else { - // Forget old targets - ClearTargets(); - // Re-draw any highlighting - if (oldCell) { - ProcessUIHandleResult( - oldCell.get(), oldCell.get(), updateFlags); - } - } - - auto oldHandle = Target(); - auto oldPosition = mTarget; - - // Now do the - // UIHANDLE HIT TEST ! - mTargets = newCell->HitTest(tpmState, GetProject()); - - mTarget = 0; - - // Find the old target's NEW place if we can - if (oldHandle) { - auto begin = mTargets.begin(), end = mTargets.end(), - iter = std::find(begin, end, oldHandle); - if (iter != end) { - size_t newPosition = iter - begin; - if (newPosition <= oldPosition) - mTarget = newPosition; - // else, some NEW hit and this position takes priority - } - } - - handle = Target(); - - mLastCell = newCell; - - if (!oldCell && oldHandle != handle) - // Did not move cell to cell, but did change the target - refreshCode = updateFlags; - - if (handle && handle != oldHandle) - handle->Enter(true); - } - - // UIHANDLE PREVIEW - // Update status message and cursor, whether dragging or not - if (handle) { - auto preview = handle->Preview( tpmState, GetProject() ); - status = preview.message; - tooltip = preview.tooltip; - pCursor = preview.cursor; - auto code = handle->GetChangeHighlight(); - handle->SetChangeHighlight(RefreshCode::RefreshNone); - refreshCode |= code; - mMouseOverUpdateFlags |= code; - } - if (newCell && - (!pCursor || status.empty() || tooltip.empty())) { - // Defaulting of cursor, tooltip, and status if there is no handle, - // or if the handle does not specify them - const auto preview = newCell->DefaultPreview( tpmState, GetProject() ); - if (!pCursor) - pCursor = preview.cursor; - if (status.empty()) - status = preview.message; - if (tooltip.empty()) - tooltip = preview.tooltip; - } - if (!pCursor) { - // Ultimate default cursor - static wxCursor defaultCursor{ wxCURSOR_DEFAULT }; - pCursor = &defaultCursor; - } - - UpdateStatusMessage(status); - -#if wxUSE_TOOLTIPS - if (tooltip != GetToolTipText()) { - // Unset first, by analogy with AButton - UnsetToolTip(); - SetToolTip(tooltip); - } -#endif - - if (pCursor) - SetCursor( *pCursor ); - - ProcessUIHandleResult( - newCell.get(), newCell.get(), refreshCode); -} - void TrackPanel::UpdateStatusMessage( const wxString &st ) { auto status = st; @@ -987,62 +721,6 @@ void TrackPanel::UpdateStatusMessage( const wxString &st ) mListener->TP_DisplayStatusMessage(status); } -bool CellularPanel::HasRotation() -{ - // Is there a nontrivial TAB key rotation? - if ( mTargets.size() > 1 ) - return true; - auto target = Target(); - return target && target->HasRotation(); -} - -bool CellularPanel::HasEscape() -{ - if (IsMouseCaptured()) - return true; - - if (mTarget + 1 == mTargets.size() && - Target() && - !Target()->HasEscape()) - return false; - - return mTargets.size() > 0; -} - -bool CellularPanel::ChangeTarget(bool forward, bool cycle) -{ - auto size = mTargets.size(); - - auto target = Target(); - if (target && target->HasRotation()) { - if(target->Rotate(forward)) - return true; - else if (cycle && (size == 1 || IsMouseCaptured())) { - // Rotate through the states of this target only. - target->Enter(forward); - return true; - } - } - - if (!cycle && - ((forward && mTarget + 1 == size) || - (!forward && mTarget == 0))) - return false; - - if (size > 1) { - if (forward) - ++mTarget; - else - mTarget += size - 1; - mTarget %= size; - if (Target()) - Target()->Enter(forward); - return true; - } - - return false; -} - void TrackPanel::UpdateSelectionDisplay() { // Full refresh since the label area may need to indicate @@ -1101,12 +779,6 @@ void TrackPanel::MessageForScreenReader(const wxString& message) mAx->MessageForScreenReader(message); } -/// Determines if a modal tool is active -bool CellularPanel::IsMouseCaptured() -{ - return mUIHandle != NULL; -} - void TrackPanel::UpdateViewIfNoTracks() { if (mTracks->empty()) @@ -1167,11 +839,6 @@ void TrackPanel::OnTrackListDeletion(wxCommandEvent & e) e.Skip(); } -void CellularPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event)) -{ - DoContextMenu(); -} - struct TrackInfo::TCPLine { using DrawFunction = void (*)( TrackPanelDrawingContext &context, @@ -1360,85 +1027,6 @@ bool TrackInfo::HideTopItem( const wxRect &rect, const wxRect &subRect, return subRect.y + subRect.height - allowance >= rect.y + limit; } -/// Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling) -void CellularPanel::HandleWheelRotation( TrackPanelMouseEvent &tpmEvent ) -{ - auto pCell = tpmEvent.pCell; - if (!pCell) - return; - - auto &event = tpmEvent.event; - double steps {}; -#if defined(__WXMAC__) && defined(EVT_MAGNIFY) - // PRL: - // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from - // the future in custom build of 3.0.2 - if (event.Magnify()) { - event.SetControlDown(true); - steps = 2 * event.GetMagnification(); - } - else -#endif - { - steps = event.m_wheelRotation / - (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0); - } - - if(event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL) { - // Two-fingered horizontal swipe on mac is treated like shift-mousewheel - event.SetShiftDown(true); - // This makes the wave move in the same direction as the fingers, and the scrollbar - // thumb moves oppositely - steps *= -1; - } - - tpmEvent.steps = steps; - - if(!event.HasAnyModifiers()) { - // We will later un-skip if we do anything, but if we don't, - // propagate the event up for the sake of the scrubber - event.Skip(); - event.ResumePropagation(wxEVENT_PROPAGATE_MAX); - } - - unsigned result = - pCell->HandleWheelRotation( tpmEvent, GetProject() ); - ProcessUIHandleResult( - pCell.get(), pCell.get(), result); -} - -void CellularPanel::OnCaptureKey(wxCommandEvent & event) -{ - mEnableTab = false; - wxKeyEvent *kevent = static_cast(event.GetEventObject()); - const auto code = kevent->GetKeyCode(); - if ( WXK_ESCAPE != code ) - HandleInterruptedDrag(); - - // Give focused cell precedence - const auto t = GetFocusedCell(); - if (t) { - const unsigned refreshResult = - t->CaptureKey(*kevent, *mViewInfo, this); - ProcessUIHandleResult(t, t, refreshResult); - event.Skip(kevent->GetSkipped()); - } - -#if 0 - // Special TAB key handling, but only if the cell didn't capture it - if ( !(t && !kevent->GetSkipped()) && - WXK_TAB == code && HasRotation() ) { - // Override TAB navigation in wxWidgets, by not skipping - event.Skip(false); - mEnableTab = true; - return; - } - else -#endif - if (!t) - event.Skip(); -} - void TrackPanel::OnKeyDown(wxKeyEvent & event) { switch (event.GetKeyCode()) @@ -1459,121 +1047,6 @@ void TrackPanel::OnKeyDown(wxKeyEvent & event) } } -void CellularPanel::OnKeyDown(wxKeyEvent & event) -{ - switch (event.GetKeyCode()) - { - case WXK_ESCAPE: - if(HandleEscapeKey(true)) - // Don't skip the event, eat it so that - // AudacityApp does not also stop any playback. - return; - else - break; - - case WXK_ALT: - case WXK_SHIFT: - case WXK_CONTROL: -#ifdef __WXOSX__ - case WXK_RAW_CONTROL: -#endif - HandleModifierKey(); - break; - -#if 0 - case WXK_TAB: - if ( mEnableTab && HasRotation() ) { - ChangeTarget( !event.ShiftDown(), true ); - HandleCursorForPresentMouseState(false); - return; - } - else - break; -#endif - } - - const auto t = GetFocusedCell(); - - if (t) { - const unsigned refreshResult = - t->KeyDown(event, *mViewInfo, this); - ProcessUIHandleResult(t, t, refreshResult); - } - else - event.Skip(); -} - -void CellularPanel::OnChar(wxKeyEvent & event) -{ - switch (event.GetKeyCode()) - { - case WXK_ESCAPE: - case WXK_ALT: - case WXK_SHIFT: - case WXK_CONTROL: - case WXK_PAGEUP: - case WXK_PAGEDOWN: - return; - } - - const auto t = GetFocusedCell(); - if (t) { - const unsigned refreshResult = - t->Char(event, *mViewInfo, this); - ProcessUIHandleResult(t, t, refreshResult); - } - else - event.Skip(); -} - -void CellularPanel::OnKeyUp(wxKeyEvent & event) -{ - bool didSomething = false; - switch (event.GetKeyCode()) - { - case WXK_ESCAPE: - didSomething = HandleEscapeKey(false); - break; - - case WXK_ALT: - case WXK_SHIFT: - case WXK_CONTROL: -#ifdef __WXOSX__ - case WXK_RAW_CONTROL: -#endif - HandleModifierKey(); - break; - } - - if (didSomething) - return; - - const auto t = GetFocusedCell(); - if (t) { - const unsigned refreshResult = - t->KeyUp(event, *mViewInfo, this); - ProcessUIHandleResult(t, t, refreshResult); - return; - } - - event.Skip(); -} - -/// Should handle the case when the mouse capture is lost. -void CellularPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) -{ - ClearTargets(); - - // This is bad. We are lying abou the event by saying it is a mouse up. - wxMouseEvent e(wxEVT_LEFT_UP); - e.SetId( kCaptureLostEventId ); - - e.m_x = mMouseMostRecentX; - e.m_y = mMouseMostRecentY; - - OnMouseEvent(e); -} - void TrackPanel::OnMouseEvent(wxMouseEvent & event) { if (event.LeftDown()) { @@ -1601,195 +1074,6 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event) event.Skip(); } -/// This handles just generic mouse events. Then, based -/// on our current state, we forward the mouse events to -/// various interested parties. -void CellularPanel::OnMouseEvent(wxMouseEvent & event) -try -{ - const auto foundCell = FindCell( event.m_x, event.m_y ); - auto &rect = foundCell.rect; - auto &pCell = foundCell.pCell; - - const auto size = GetSize(); - TrackPanelMouseEvent tpmEvent{ event, rect, size, pCell }; - -#if defined(__WXMAC__) && defined(EVT_MAGNIFY) - // PRL: - // Pinch and spread implemented in wxWidgets 3.1.0, or cherry-picked from - // the future in custom build of 3.0.2 - if (event.Magnify()) { - HandleWheelRotation( tpmEvent ); - } -#endif - - // If a mouse event originates from a keyboard context menu event then - // event.GetPosition() == wxDefaultPosition. wxContextMenu events are handled in - // CellularPanel::OnContextMenu(), and therefore associated mouse events are ignored here. - // Not ignoring them was causing bug 613: the mouse events were interpreted as clicking - // outside the tracks. - if (event.GetPosition() == wxDefaultPosition && (event.RightDown() || event.RightUp())) { - event.Skip(); - return; - } - - if (event.m_wheelRotation != 0) - HandleWheelRotation( tpmEvent ); - - if (event.LeftDown() || event.LeftIsDown() || event.Moving()) { - // Skip, even if we do something, so that the left click or drag - // may have an additional effect in the scrubber. - event.Skip(); - event.ResumePropagation(wxEVENT_PROPAGATE_MAX); - } - - mMouseMostRecentX = event.m_x; - mMouseMostRecentY = event.m_y; - - if (event.LeftDown()) { - // The activate event is used to make the - // parent window 'come alive' if it didn't have focus. - wxActivateEvent e; - GetParent()->GetEventHandler()->ProcessEvent(e); - } - - if (event.ButtonDown()) { - SetFocus(); - } - - if (event.Leaving()) - { - if ( !mUIHandle ) - ClearTargets(); - - auto buttons = - // Bug 1325: button state in Leaving events is unreliable on Mac. - // Poll the global state instead. - // event.ButtonIsDown(wxMOUSE_BTN_ANY); - ::wxGetMouseState().ButtonIsDown(wxMOUSE_BTN_ANY); - - if(!buttons) { - CancelDragging(); - -#if defined(__WXMAC__) - - // We must install the cursor ourselves since the window under - // the mouse is no longer this one and wx2.8.12 makes that check. - // Should re-evaluate with wx3. - wxSTANDARD_CURSOR->MacInstall(); -#endif - } - } - - if (mUIHandle) { - auto pClickedCell = mpClickedCell.lock(); - if (event.Dragging()) { - // UIHANDLE DRAG - // copy shared_ptr for safety, as in HandleClick - auto handle = mUIHandle; - const UIHandle::Result refreshResult = - handle->Drag( tpmEvent, GetProject() ); - ProcessUIHandleResult - (pClickedCell.get(), pCell.get(), refreshResult); - mMouseOverUpdateFlags |= refreshResult; - if (refreshResult & RefreshCode::Cancelled) { - // Drag decided to abort itself - mUIHandle.reset(), handle.reset(), ClearTargets(); - mpClickedCell.reset(); - Uncapture( &event ); - } - else { - UpdateMouseState(event); - TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell }; - HandleMotion( tpmState ); - } - } - else if (event.ButtonUp()) { - // UIHANDLE RELEASE - unsigned moreFlags = mMouseOverUpdateFlags; - UIHandle::Result refreshResult = - mUIHandle->Release( tpmEvent, GetProject(), this ); - ProcessUIHandleResult - (pClickedCell.get(), pCell.get(), - refreshResult | moreFlags); - mUIHandle.reset(), ClearTargets(); - mpClickedCell.reset(); - // will also Uncapture() below - } - } - else if ( event.GetEventType() == wxEVT_MOTION ) - // Update status message and cursor, not during drag - // consider it not a drag, even if button is down during motion, if - // mUIHandle is null, as it becomes during interrupted drag - // (e.g. by hitting space to play while dragging an envelope point) - HandleMotion( event ); - else if ( event.ButtonDown() || event.ButtonDClick() ) - HandleClick( tpmEvent ); - - if (event.ButtonDown() && IsMouseCaptured()) { - if (!HasCapture()) - CaptureMouse(); - } - - if (event.ButtonUp()) - Uncapture(); -} -catch( ... ) -{ - // Abort any dragging, as if by hitting Esc - if ( CancelDragging() ) - ; - else { - Uncapture(); - Refresh(false); - } - throw; -} - -void CellularPanel::HandleClick( const TrackPanelMouseEvent &tpmEvent ) -{ - auto pCell = tpmEvent.pCell; - - // Do hit test once more, in case the button really pressed was not the - // one "anticipated." - { - TrackPanelMouseState tpmState{ - tpmEvent.event, - tpmEvent.rect, - tpmEvent.pCell - }; - HandleMotion( tpmState ); - } - - mUIHandle = Target(); - - if (mUIHandle) { - // UIHANDLE CLICK - // Make another shared pointer to the handle, in case recursive - // event dispatching otherwise tries to delete the handle. - auto handle = mUIHandle; - UIHandle::Result refreshResult = - handle->Click( tpmEvent, GetProject() ); - if (refreshResult & RefreshCode::Cancelled) - mUIHandle.reset(), handle.reset(), ClearTargets(); - else { - mpClickedCell = pCell; - - // Perhaps the clicked handle wants to update cursor and state message - // after a click. - TrackPanelMouseState tpmState{ - tpmEvent.event, - tpmEvent.rect, - tpmEvent.pCell - }; - HandleMotion( tpmState ); - } - ProcessUIHandleResult( - pCell.get(), pCell.get(), refreshResult); - mMouseOverUpdateFlags |= refreshResult; - } -} - double TrackPanel::GetMostRecentXPos() { return mViewInfo->PositionToTime(MostRecentXCoord(), GetLabelWidth()); @@ -2628,26 +1912,6 @@ void TrackPanel::OnTrackMenu(Track *t) CellularPanel::DoContextMenu( t ); } -void CellularPanel::DoContextMenu( TrackPanelCell *pCell ) -{ - if( !pCell ) { - pCell = GetFocusedCell(); - if( !pCell ) - return; - } - - const auto delegate = pCell->ContextMenuDelegate(); - if (!delegate) - return; - - auto rect = FindRect( *delegate ); - const UIHandle::Result refreshResult = - delegate->DoContextMenu(rect, this, NULL); - - // To do: use safer shared_ptr to pCell - ProcessUIHandleResult(pCell, pCell, refreshResult); -} - Track * TrackPanel::GetFirstSelectedTrack() { @@ -2944,20 +2208,6 @@ void TrackPanel::SetFocusedTrack( Track *t ) } } -void CellularPanel::OnSetFocus(wxFocusEvent & WXUNUSED(event)) -{ - SetFocusedCell(); -} - -void CellularPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event)) -{ - if (AudacityProject::HasKeyboardCapture(this)) - { - AudacityProject::ReleaseKeyboard(this); - } - Refresh( false); -} - /********************************************************************** TrackInfo code is destined to move out of this file. diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 6a3c00cff..9df28299c 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -22,7 +22,7 @@ #include "SelectedRegion.h" -#include "widgets/OverlayPanel.h" +#include "CellularPanel.h" #include "SelectionState.h" @@ -34,7 +34,6 @@ class SpectrumAnalyst; class Track; class TrackList; class TrackPanel; -class TrackPanelCell; class TrackArtist; class Ruler; class SnapManager; @@ -43,20 +42,13 @@ class LWSlider; class ControlToolBar; //Needed because state of controls can affect what gets drawn. class ToolsToolBar; //Needed because state of controls can affect what gets drawn. class MixerBoard; -class AudacityProject; class TrackPanelAx; class TrackPanelCellIterator; -struct TrackPanelMouseEvent; -struct TrackPanelMouseState; - -class ViewInfo; class NoteTrack; class WaveTrack; class WaveClip; -class UIHandle; -using UIHandlePtr = std::shared_ptr; // Declared elsewhere, to reduce compilation dependencies class TrackPanelListener; @@ -242,127 +234,6 @@ private: const int DragThreshold = 3;// Anything over 3 pixels is a drag, else a click. -// This class manages a panel divided into a number of sub-rectangles called -// cells, that each implement hit tests returning click-drag-release handler -// objects, and other services. -// It has no dependency on the Track class. -class AUDACITY_DLL_API CellularPanel : public OverlayPanel { -public: - CellularPanel(wxWindow * parent, wxWindowID id, - const wxPoint & pos, - const wxSize & size, - ViewInfo *viewInfo, - // default as for wxPanel: - long style = wxTAB_TRAVERSAL | wxNO_BORDER) - : OverlayPanel(parent, id, pos, size, style) - , mViewInfo( viewInfo ) - {} - - // Overridables: - - virtual AudacityProject *GetProject() const = 0; - - // Find track info by coordinate - struct FoundCell { - std::shared_ptr pCell; - wxRect rect; - }; - virtual FoundCell FindCell(int mouseX, int mouseY) = 0; - virtual wxRect FindRect(const TrackPanelCell &cell) = 0; - virtual TrackPanelCell *GetFocusedCell() = 0; - virtual void SetFocusedCell() = 0; - - virtual void ProcessUIHandleResult - (TrackPanelCell *pClickedCell, TrackPanelCell *pLatestCell, - unsigned refreshResult) = 0; - - virtual void UpdateStatusMessage( const wxString & ) = 0; - -public: - UIHandlePtr Target() - { - if (mTargets.size()) - return mTargets[mTarget]; - else - return {}; - } - - bool IsMouseCaptured(); - - wxCoord MostRecentXCoord() const { return mMouseMostRecentX; } - - void HandleCursorForPresentMouseState(bool doHit = true); - -protected: - bool HasEscape(); - bool CancelDragging(); - void ClearTargets() - { - // Forget the rotation of hit test candidates when the mouse moves from - // cell to cell or outside of the panel entirely. - mLastCell.reset(); - mTargets.clear(); - mTarget = 0; - mMouseOverUpdateFlags = 0; - } - void DoContextMenu( TrackPanelCell *pCell = nullptr ); - -private: - bool HasRotation(); - bool ChangeTarget(bool forward, bool cycle); - - void OnMouseEvent(wxMouseEvent & event); - void OnCaptureLost(wxMouseCaptureLostEvent & event); - void OnCaptureKey(wxCommandEvent & event); - void OnKeyDown(wxKeyEvent & event); - void OnChar(wxKeyEvent & event); - void OnKeyUp(wxKeyEvent & event); - - void OnSetFocus(wxFocusEvent & event); - void OnKillFocus(wxFocusEvent & event); - - void OnContextMenu(wxContextMenuEvent & event); - - void HandleInterruptedDrag(); - void Uncapture( wxMouseState *pState = nullptr ); - bool HandleEscapeKey(bool down); - void UpdateMouseState(const wxMouseState &state); - void HandleModifierKey(); - - void HandleClick( const TrackPanelMouseEvent &tpmEvent ); - void HandleWheelRotation( TrackPanelMouseEvent &tpmEvent ); - - void HandleMotion( wxMouseState &state, bool doHit = true ); - void HandleMotion - ( const TrackPanelMouseState &tpmState, bool doHit = true ); - - -protected: - ViewInfo *mViewInfo; - -private: - UIHandlePtr mUIHandle; - - std::weak_ptr mLastCell; - std::vector mTargets; - size_t mTarget {}; - unsigned mMouseOverUpdateFlags{}; - -protected: - // To do: make a drawing method and make this private - wxMouseState mLastMouseState; - -private: - int mMouseMostRecentX; - int mMouseMostRecentY; - - std::weak_ptr mpClickedCell; - - bool mEnableTab{}; - - DECLARE_EVENT_TABLE() -}; - class AUDACITY_DLL_API TrackPanel final : public CellularPanel { public: @@ -606,7 +477,7 @@ protected: DECLARE_EVENT_TABLE() void ProcessUIHandleResult - (TrackPanelCell *pClickedCell, TrackPanelCell *pLatestCell, + (TrackPanelCell *pClickedTrack, TrackPanelCell *pLatestCell, unsigned refreshResult) override; void UpdateStatusMessage( const wxString &status ) override; diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 5adb717ab..bc59e1dc5 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -138,6 +138,7 @@ + @@ -481,6 +482,7 @@ + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index c046afba1..64f352cc6 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -1082,6 +1082,9 @@ src\widgets + + src + @@ -2158,6 +2161,9 @@ src\widgets + + src +