1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-16 06:03:49 +01:00

Move class CellularPanel into its own files

This commit is contained in:
Paul Licameli
2018-08-04 13:06:55 -04:00
parent 7f84e71324
commit f6609e5ffb
9 changed files with 1023 additions and 921 deletions

View File

@@ -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<Track> 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<wxKeyEvent *>(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.