Horizontal/vertical zoom for Simulator plots
ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
This commit is contained in:
+427
-287
@@ -5,9 +5,9 @@
|
||||
// Maintainer: Davide Rondini
|
||||
// Contributors: Jose Luis Blanco, Val Greene, Maciej Suminski, Tomasz Wlostowski
|
||||
// Created: 21/07/2003
|
||||
// Last edit: 2023
|
||||
// Last edit: 2024
|
||||
// Copyright: (c) David Schalig, Davide Rondini
|
||||
// Copyright (c) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
// Copyright (c) 2021-2024 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
// Licence: wxWindows licence
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1367,82 +1367,14 @@ EVT_MENU( mpID_ZOOM_REDO, mpWindow::onZoomRedo )
|
||||
END_EVENT_TABLE()
|
||||
|
||||
mpWindow::mpWindow() :
|
||||
wxWindow(),
|
||||
m_minX( 0.0 ),
|
||||
m_maxX( 0.0 ),
|
||||
m_minY( 0.0 ),
|
||||
m_maxY( 0.0 ),
|
||||
m_scaleX( 1.0 ),
|
||||
m_scaleY( 1.0 ),
|
||||
m_posX( 0.0 ),
|
||||
m_posY( 0.0 ),
|
||||
m_scrX( 64 ),
|
||||
m_scrY( 64 ),
|
||||
m_clickedX( 0 ),
|
||||
m_clickedY( 0 ),
|
||||
m_yLocked( false ),
|
||||
m_desiredXmin( 0.0 ),
|
||||
m_desiredXmax( 1.0 ),
|
||||
m_desiredYmin( 0.0 ),
|
||||
m_desiredYmax( 1.0 ),
|
||||
m_marginTop( 0 ),
|
||||
m_marginRight( 0 ),
|
||||
m_marginBottom( 0 ),
|
||||
m_marginLeft( 0 ),
|
||||
m_last_lx( 0 ),
|
||||
m_last_ly( 0 ),
|
||||
m_buff_bmp( nullptr ),
|
||||
m_enableDoubleBuffer( false ),
|
||||
m_enableMouseNavigation( true ),
|
||||
m_enableMouseWheelPan( false ),
|
||||
m_enableLimitedView( false ),
|
||||
m_movingInfoLayer( nullptr ),
|
||||
m_zooming( false )
|
||||
mpWindow( DelegatingContructorTag() )
|
||||
{
|
||||
if( wxGraphicsContext *ctx = m_buff_dc.GetGraphicsContext() )
|
||||
{
|
||||
if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST )
|
||||
|| !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) )
|
||||
{
|
||||
ctx->SetInterpolationQuality( wxINTERPOLATION_FAST );
|
||||
}
|
||||
|
||||
ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT );
|
||||
}
|
||||
initializeGraphicsContext();
|
||||
}
|
||||
|
||||
mpWindow::mpWindow( wxWindow* parent, wxWindowID id ) :
|
||||
wxWindow( parent, id, wxDefaultPosition, wxDefaultSize, 0, wxT( "mathplot" ) ),
|
||||
m_minX( 0.0 ),
|
||||
m_maxX( 0.0 ),
|
||||
m_minY( 0.0 ),
|
||||
m_maxY( 0.0 ),
|
||||
m_scaleX( 1.0 ),
|
||||
m_scaleY( 1.0 ),
|
||||
m_posX( 0.0 ),
|
||||
m_posY( 0.0 ),
|
||||
m_scrX( 64 ),
|
||||
m_scrY( 64 ),
|
||||
m_clickedX( 0 ),
|
||||
m_clickedY( 0 ),
|
||||
m_yLocked( false ),
|
||||
m_desiredXmin( 0.0 ),
|
||||
m_desiredXmax( 1.0 ),
|
||||
m_desiredYmin( 0.0 ),
|
||||
m_desiredYmax( 1.0 ),
|
||||
m_marginTop( 0 ),
|
||||
m_marginRight( 0 ),
|
||||
m_marginBottom( 0 ),
|
||||
m_marginLeft( 0 ),
|
||||
m_last_lx( 0 ),
|
||||
m_last_ly( 0 ),
|
||||
m_buff_bmp( nullptr ),
|
||||
m_enableDoubleBuffer( false ),
|
||||
m_enableMouseNavigation( true ),
|
||||
m_enableMouseWheelPan( false ),
|
||||
m_enableLimitedView( false ),
|
||||
m_movingInfoLayer( nullptr ),
|
||||
m_zooming( false )
|
||||
mpWindow( DelegatingContructorTag(),
|
||||
parent, id, wxDefaultPosition, wxDefaultSize, 0, wxT( "mathplot" ) )
|
||||
{
|
||||
m_popmenu.Append( mpID_ZOOM_UNDO, _( "Undo Last Zoom" ), _( "Return zoom to level prior to last zoom action" ) );
|
||||
m_popmenu.Append( mpID_ZOOM_REDO, _( "Redo Last Zoom" ), _( "Return zoom to level prior to last zoom undo" ) );
|
||||
@@ -1462,17 +1394,7 @@ mpWindow::mpWindow( wxWindow* parent, wxWindowID id ) :
|
||||
// J.L.Blanco: Eliminates the "flick" with the double buffer.
|
||||
SetBackgroundStyle( wxBG_STYLE_CUSTOM );
|
||||
|
||||
if( wxGraphicsContext* ctx = m_buff_dc.GetGraphicsContext() )
|
||||
{
|
||||
if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST )
|
||||
|| !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) )
|
||||
{
|
||||
ctx->SetInterpolationQuality( wxINTERPOLATION_FAST );
|
||||
}
|
||||
|
||||
ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT );
|
||||
}
|
||||
|
||||
initializeGraphicsContext();
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
@@ -1524,56 +1446,37 @@ void mpWindow::onMouseWheel( wxMouseEvent& event )
|
||||
return;
|
||||
}
|
||||
|
||||
int change = event.GetWheelRotation();
|
||||
const int axis = event.GetWheelAxis();
|
||||
double changeUnitsX = change / m_scaleX;
|
||||
double changeUnitsY = change / m_scaleY;
|
||||
const wxMouseWheelAxis axis = event.GetWheelAxis();
|
||||
const int modifiers = event.GetModifiers();
|
||||
MouseWheelAction action = MouseWheelAction::NONE;
|
||||
|
||||
if( ( !m_enableMouseWheelPan && ( event.ControlDown() || event.ShiftDown() ) )
|
||||
|| ( m_enableMouseWheelPan && !event.ControlDown() ) )
|
||||
if( axis == wxMOUSE_WHEEL_HORIZONTAL )
|
||||
{
|
||||
// Scrolling
|
||||
if( m_enableMouseWheelPan )
|
||||
{
|
||||
if( axis == wxMOUSE_WHEEL_HORIZONTAL || event.ShiftDown() )
|
||||
{
|
||||
SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX,
|
||||
m_desiredXmin + changeUnitsX );
|
||||
}
|
||||
else if( !m_yLocked )
|
||||
{
|
||||
SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY,
|
||||
m_desiredYmin + changeUnitsY );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( event.ControlDown() )
|
||||
{
|
||||
SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX,
|
||||
m_desiredXmin + changeUnitsX );
|
||||
}
|
||||
else if( !m_yLocked )
|
||||
{
|
||||
SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY,
|
||||
m_desiredYmin + changeUnitsY );
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAll();
|
||||
action = m_mouseWheelActions.horizontal;
|
||||
}
|
||||
else if( modifiers == wxMOD_NONE )
|
||||
{
|
||||
action = m_mouseWheelActions.verticalUnmodified;
|
||||
}
|
||||
else if( modifiers == wxMOD_CONTROL )
|
||||
{
|
||||
action = m_mouseWheelActions.verticalWithCtrl;
|
||||
}
|
||||
else if( modifiers == wxMOD_SHIFT )
|
||||
{
|
||||
action = m_mouseWheelActions.verticalWithShift;
|
||||
}
|
||||
else if( modifiers == wxMOD_ALT )
|
||||
{
|
||||
action = m_mouseWheelActions.verticalWithAlt;
|
||||
}
|
||||
else
|
||||
{
|
||||
// zoom in/out
|
||||
wxPoint clickPt( event.GetX(), event.GetY() );
|
||||
|
||||
if( event.GetWheelRotation() > 0 )
|
||||
ZoomIn( clickPt );
|
||||
else
|
||||
ZoomOut( clickPt );
|
||||
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
PerformMouseWheelAction( event, action );
|
||||
}
|
||||
|
||||
|
||||
@@ -1716,107 +1619,151 @@ void mpWindow::Fit()
|
||||
|
||||
// JL
|
||||
void mpWindow::Fit( double xMin, double xMax, double yMin, double yMax,
|
||||
const wxCoord* printSizeX, const wxCoord* printSizeY )
|
||||
const wxCoord* printSizeX, const wxCoord* printSizeY,
|
||||
wxOrientation directions )
|
||||
{
|
||||
const bool isPrinting = printSizeX != nullptr && printSizeY != nullptr;
|
||||
|
||||
// Save desired borders:
|
||||
m_desiredXmin = xMin; m_desiredXmax = xMax;
|
||||
m_desiredYmin = yMin; m_desiredYmax = yMax;
|
||||
double newDesiredXmin = xMin;
|
||||
double newDesiredXmax = xMax;
|
||||
double newDesiredYmin = yMin;
|
||||
double newDesiredYmax = yMax;
|
||||
|
||||
// Give a small margin to plot area
|
||||
double xExtra = fabs( xMax - xMin ) * 0.00;
|
||||
double yExtra = fabs( yMax - yMin ) * 0.03;
|
||||
// Provide a gap between the extrema of the curve and the top/bottom edges of the
|
||||
// plot area. Not to be confused with the left/right/top/bottom margins outside the plot area.
|
||||
const double xGap = fabs( xMax - xMin ) * m_leftRightPlotGapFactor;
|
||||
const double yGap = fabs( yMax - yMin ) * m_topBottomPlotGapFactor;
|
||||
xMin -= xGap;
|
||||
xMax += xGap;
|
||||
yMin -= yGap;
|
||||
yMax += yGap;
|
||||
|
||||
xMin -= xExtra;
|
||||
xMax += xExtra;
|
||||
yMin -= yExtra;
|
||||
yMax += yExtra;
|
||||
int newScrX = m_scrX;
|
||||
int newScrY = m_scrY;
|
||||
|
||||
if( printSizeX != nullptr && printSizeY != nullptr )
|
||||
if( isPrinting )
|
||||
{
|
||||
// Printer:
|
||||
m_scrX = *printSizeX;
|
||||
m_scrY = *printSizeY;
|
||||
newScrX = *printSizeX;
|
||||
newScrY = *printSizeY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal case (screen):
|
||||
GetClientSize( &m_scrX, &m_scrY );
|
||||
GetClientSize( &newScrX, &newScrY );
|
||||
}
|
||||
|
||||
double Ax = xMax - xMin;
|
||||
double Ay = yMax - yMin;
|
||||
// Compute the width/height in pixels for the plot area.
|
||||
const int plotScreenWidth = newScrX - m_marginLeft - m_marginRight;
|
||||
const int plotScreenHeight = newScrY - m_marginTop - m_marginBottom;
|
||||
|
||||
m_scaleX = (Ax != 0) ? (m_scrX - m_marginLeft - m_marginRight) / Ax : 1;
|
||||
m_scaleY = (Ay != 0) ? (m_scrY - m_marginTop - m_marginBottom) / Ay : 1;
|
||||
// Adjust scale so that desired X/Y span plus extra gap fits in the plot area
|
||||
double desiredSpanX = xMax - xMin;
|
||||
double desiredSpanY = yMax - yMin;
|
||||
double newScaleX = (desiredSpanX != 0) ? double(plotScreenWidth) / desiredSpanX : 1;
|
||||
double newScaleY = (desiredSpanY != 0) ? double(plotScreenHeight) / desiredSpanY : 1;
|
||||
|
||||
// Adjusts corner coordinates: This should be simply:
|
||||
// m_posX = m_minX;
|
||||
// m_posY = m_maxY;
|
||||
// But account for centering if we have lock aspect:
|
||||
m_posX = (xMin + xMax) / 2 - ( (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft ) /
|
||||
m_scaleX;
|
||||
m_posY = (yMin + yMax) / 2 + ( (m_scrY - m_marginTop - m_marginBottom) / 2 + m_marginTop ) /
|
||||
m_scaleY;
|
||||
// Adjust corner coordinates:
|
||||
// Upstream's aspect lock code has been removed, so no need to account for centering.
|
||||
double newPosX = xMin - (m_marginLeft / newScaleX);
|
||||
double newPosY = yMax + (m_marginTop / newScaleY);
|
||||
|
||||
// It is VERY IMPORTANT to DO NOT call Refresh if we are drawing to the printer!!
|
||||
// Commit above changes to member variables only if enabled for their respective dimension.
|
||||
if( ((directions & wxHORIZONTAL) != 0) || isPrinting )
|
||||
{
|
||||
// Don't commit the passed desired bounds when printing
|
||||
if (!isPrinting)
|
||||
{
|
||||
m_desiredXmin = newDesiredXmin;
|
||||
m_desiredXmax = newDesiredXmax;
|
||||
}
|
||||
|
||||
m_scrX = newScrX;
|
||||
m_scaleX = newScaleX;
|
||||
m_posX = newPosX;
|
||||
}
|
||||
|
||||
if( ((directions & wxVERTICAL) != 0) || isPrinting )
|
||||
{
|
||||
// Don't commit the passed desired bounds when printing
|
||||
if (!isPrinting)
|
||||
{
|
||||
m_desiredYmin = newDesiredYmin;
|
||||
m_desiredYmax = newDesiredYmax;
|
||||
}
|
||||
|
||||
m_scrY = newScrY;
|
||||
m_scaleY = newScaleY;
|
||||
m_posY = newPosY;
|
||||
}
|
||||
|
||||
// It is VERY IMPORTANT to NOT call Refresh if we are drawing to the printer!!
|
||||
// Otherwise, the DC dimensions will be those of the window instead of the printer device
|
||||
if( printSizeX == nullptr || printSizeY == nullptr )
|
||||
// The caller wanting to print should perform another Fit() afterwards to restore this
|
||||
// object's state.
|
||||
if( !isPrinting )
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::AdjustLimitedView()
|
||||
void mpWindow::AdjustLimitedView( wxOrientation directions )
|
||||
{
|
||||
if( !m_enableLimitedView )
|
||||
return;
|
||||
|
||||
// m_min and m_max are plot limits for curves
|
||||
// xMin, xMax, yMin, yMax are the full limits (plot limit + margin)
|
||||
const double xMin = m_minX - m_marginLeft / m_scaleX;
|
||||
const double xMax = m_maxX + m_marginRight / m_scaleX;
|
||||
const double yMin = m_minY - m_marginBottom / m_scaleY;
|
||||
const double yMax = m_maxY + m_marginTop / m_scaleY;
|
||||
// The m_desired* members are expressed in plot coordinates.
|
||||
// They should be clamped against their respective m_minX, m_maxX, m_minY, m_maxY limits.
|
||||
|
||||
if( m_desiredXmin < xMin )
|
||||
if( (directions & wxHORIZONTAL) != 0 )
|
||||
{
|
||||
double diff = xMin - m_desiredXmin;
|
||||
m_posX += diff;
|
||||
m_desiredXmax += diff;
|
||||
m_desiredXmin = xMin;
|
||||
if( m_desiredXmin < m_minX )
|
||||
{
|
||||
double diff = m_minX - m_desiredXmin;
|
||||
m_posX += diff;
|
||||
m_desiredXmax += diff;
|
||||
m_desiredXmin = m_minX;
|
||||
}
|
||||
|
||||
if( m_desiredXmax > m_maxX )
|
||||
{
|
||||
double diff = m_desiredXmax - m_maxX;
|
||||
m_posX -= diff;
|
||||
m_desiredXmin -= diff;
|
||||
m_desiredXmax = m_maxX;
|
||||
}
|
||||
}
|
||||
|
||||
if( m_desiredXmax > xMax )
|
||||
if( (directions & wxVERTICAL) != 0 )
|
||||
{
|
||||
double diff = m_desiredXmax - xMax;
|
||||
m_posX -= diff;
|
||||
m_desiredXmin -= diff;
|
||||
m_desiredXmax = xMax;
|
||||
}
|
||||
if( m_desiredYmin < m_minY )
|
||||
{
|
||||
double diff = m_minY - m_desiredYmin;
|
||||
m_posY += diff;
|
||||
m_desiredYmax += diff;
|
||||
m_desiredYmin = m_minY;
|
||||
}
|
||||
|
||||
if( m_desiredYmin < yMin )
|
||||
{
|
||||
double diff = yMin - m_desiredYmin;
|
||||
m_posY += diff;
|
||||
m_desiredYmax += diff;
|
||||
m_desiredYmin = yMin;
|
||||
}
|
||||
|
||||
if( m_desiredYmax > yMax )
|
||||
{
|
||||
double diff = m_desiredYmax - yMax;
|
||||
m_posY -= diff;
|
||||
m_desiredYmin -= diff;
|
||||
m_desiredYmax = yMax;
|
||||
if( m_desiredYmax > m_maxY )
|
||||
{
|
||||
double diff = m_desiredYmax - m_maxY;
|
||||
m_posY -= diff;
|
||||
m_desiredYmin -= diff;
|
||||
m_desiredYmax = m_maxY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool mpWindow::SetXView( double pos, double desiredMax, double desiredMin )
|
||||
{
|
||||
// TODO (ecorm): Investigate X scale flickering when panning at minimum zoom level
|
||||
// Possible cause: When AdjustLimitedView subtracts the out-of-bound delta, it does not
|
||||
// revert back to the exact same original coordinates due to floating point rounding errors.
|
||||
m_posX = pos;
|
||||
m_desiredXmax = desiredMax;
|
||||
m_desiredXmin = desiredMin;
|
||||
AdjustLimitedView();
|
||||
AdjustLimitedView( wxHORIZONTAL );
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1827,7 +1774,7 @@ bool mpWindow::SetYView( double pos, double desiredMax, double desiredMin )
|
||||
m_posY = pos;
|
||||
m_desiredYmax = desiredMax;
|
||||
m_desiredYmin = desiredMin;
|
||||
AdjustLimitedView();
|
||||
AdjustLimitedView( wxVERTICAL );
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1835,116 +1782,28 @@ bool mpWindow::SetYView( double pos, double desiredMax, double desiredMin )
|
||||
|
||||
void mpWindow::ZoomIn( const wxPoint& centerPoint )
|
||||
{
|
||||
ZoomIn( centerPoint, zoomIncrementalFactor );
|
||||
ZoomIn( centerPoint, zoomIncrementalFactor, wxBOTH );
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::ZoomIn( const wxPoint& centerPoint, double zoomFactor )
|
||||
void mpWindow::ZoomIn( const wxPoint& centerPoint, double zoomFactor, wxOrientation directions )
|
||||
{
|
||||
pushZoomUndo( { m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax } );
|
||||
|
||||
wxPoint c( centerPoint );
|
||||
|
||||
if( c == wxDefaultPosition )
|
||||
{
|
||||
GetClientSize( &m_scrX, &m_scrY );
|
||||
c.x = (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft; // c.x = m_scrX/2;
|
||||
c.y = (m_scrY - m_marginTop - m_marginBottom) / 2 - m_marginTop; // c.y = m_scrY/2;
|
||||
}
|
||||
else
|
||||
{
|
||||
c.x = std::max( c.x, m_marginLeft );
|
||||
c.x = std::min( c.x, m_scrX - m_marginRight );
|
||||
c.y = std::max( c.y, m_marginTop );
|
||||
c.y = std::min( c.y, m_scrY - m_marginBottom );
|
||||
}
|
||||
|
||||
// Preserve the position of the clicked point:
|
||||
double prior_layer_x = p2x( c.x );
|
||||
double prior_layer_y = p2y( c.y );
|
||||
|
||||
// Zoom in:
|
||||
const double MAX_SCALE = 1e6;
|
||||
double newScaleX = m_scaleX * zoomFactor;
|
||||
double newScaleY = m_scaleY * zoomFactor;
|
||||
|
||||
// Baaaaad things happen when you zoom in too much..
|
||||
if( newScaleX <= MAX_SCALE && newScaleY <= MAX_SCALE )
|
||||
{
|
||||
m_scaleX = newScaleX;
|
||||
|
||||
if( !m_yLocked )
|
||||
m_scaleY = newScaleY;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust the new m_posx/y:
|
||||
m_posX = prior_layer_x - c.x / m_scaleX;
|
||||
|
||||
if( !m_yLocked )
|
||||
m_posY = prior_layer_y + c.y / m_scaleY;
|
||||
|
||||
m_desiredXmin = m_posX;
|
||||
m_desiredXmax = m_posX + (m_scrX - m_marginLeft - m_marginRight) / m_scaleX;
|
||||
m_desiredYmax = m_posY;
|
||||
m_desiredYmin = m_posY - (m_scrY - m_marginTop - m_marginBottom) / m_scaleY;
|
||||
AdjustLimitedView();
|
||||
UpdateAll();
|
||||
DoZoom( centerPoint, zoomFactor, directions );
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::ZoomOut( const wxPoint& centerPoint )
|
||||
{
|
||||
ZoomOut( centerPoint, zoomIncrementalFactor );
|
||||
ZoomOut( centerPoint, zoomIncrementalFactor, wxBOTH );
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::ZoomOut( const wxPoint& centerPoint, double zoomFactor )
|
||||
void mpWindow::ZoomOut( const wxPoint& centerPoint, double zoomFactor,
|
||||
wxOrientation directions )
|
||||
{
|
||||
pushZoomUndo( { m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax } );
|
||||
|
||||
wxPoint c( centerPoint );
|
||||
|
||||
if( c == wxDefaultPosition )
|
||||
{
|
||||
GetClientSize( &m_scrX, &m_scrY );
|
||||
c.x = (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft;
|
||||
c.y = (m_scrY - m_marginTop - m_marginBottom) / 2 - m_marginTop;
|
||||
}
|
||||
|
||||
// Preserve the position of the clicked point:
|
||||
double prior_layer_x = p2x( c.x );
|
||||
double prior_layer_y = p2y( c.y );
|
||||
|
||||
// Zoom out:
|
||||
m_scaleX = m_scaleX / zoomFactor;
|
||||
|
||||
if( !m_yLocked )
|
||||
m_scaleY = m_scaleY / zoomFactor;
|
||||
|
||||
// Adjust the new m_posx/y:
|
||||
m_posX = prior_layer_x - c.x / m_scaleX;
|
||||
|
||||
if( !m_yLocked )
|
||||
m_posY = prior_layer_y + c.y / m_scaleY;
|
||||
|
||||
m_desiredXmin = m_posX;
|
||||
m_desiredXmax = m_posX + (m_scrX - m_marginLeft - m_marginRight) / m_scaleX;
|
||||
m_desiredYmax = m_posY;
|
||||
m_desiredYmin = m_posY - (m_scrY - m_marginTop - m_marginBottom) / m_scaleY;
|
||||
|
||||
AdjustLimitedView();
|
||||
|
||||
if( !CheckXLimits( m_desiredXmax, m_desiredXmin )
|
||||
|| !CheckYLimits( m_desiredYmax, m_desiredYmin ) )
|
||||
{
|
||||
Fit();
|
||||
}
|
||||
|
||||
UpdateAll();
|
||||
if (zoomFactor == 0)
|
||||
zoomFactor = 1.0;
|
||||
DoZoom( centerPoint, 1.0 / zoomFactor, directions );
|
||||
}
|
||||
|
||||
|
||||
@@ -1952,6 +1811,20 @@ void mpWindow::ZoomRect( wxPoint p0, wxPoint p1 )
|
||||
{
|
||||
pushZoomUndo( { m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax } );
|
||||
|
||||
// Constrain given rectangle to plot area
|
||||
const int pMinX = m_marginLeft;
|
||||
const int pMaxX = m_scrX - m_marginRight;
|
||||
const int pMinY = m_marginTop;
|
||||
const int pMaxY = m_scrY - m_marginBottom;
|
||||
p0.x = std::max( p0.x, pMinX );
|
||||
p0.x = std::min( p0.x, pMaxX );
|
||||
p0.y = std::max( p0.y, pMinY );
|
||||
p0.y = std::min( p0.y, pMaxY );
|
||||
p1.x = std::max( p1.x, pMinX );
|
||||
p1.x = std::min( p1.x, pMaxX );
|
||||
p1.y = std::max( p1.y, pMinY );
|
||||
p1.y = std::min( p1.y, pMaxY );
|
||||
|
||||
// Compute the 2 corners in graph coordinates:
|
||||
double p0x = p2x( p0.x );
|
||||
double p0y = p2y( p0.y );
|
||||
@@ -1971,7 +1844,16 @@ void mpWindow::ZoomRect( wxPoint p0, wxPoint p1 )
|
||||
}
|
||||
|
||||
Fit( zoom_x_min, zoom_x_max, zoom_y_min, zoom_y_max );
|
||||
|
||||
// Even with the input rectangle contrained to the plot area, it's still possible for the
|
||||
// resulting view to exceed limits when a portion of the gap is grabbed.
|
||||
AdjustLimitedView();
|
||||
|
||||
// These additional checks are needed because AdjustLimitedView only adjusts the position
|
||||
// and not the scale.
|
||||
wxOrientation directionsNeedingRefitting = ViewNeedsRefitting( wxBOTH );
|
||||
if( directionsNeedingRefitting != 0 )
|
||||
Fit( m_minX, m_maxX, m_minY, m_maxY, nullptr, nullptr, directionsNeedingRefitting );
|
||||
}
|
||||
|
||||
|
||||
@@ -2043,6 +1925,18 @@ void mpWindow::OnCenter( wxCommandEvent& WXUNUSED( event ) )
|
||||
}
|
||||
|
||||
|
||||
mpWindow::MouseWheelActionSet mpWindow::defaultMouseWheelActions()
|
||||
{
|
||||
MouseWheelActionSet actions;
|
||||
actions.verticalUnmodified = MouseWheelAction::ZOOM;
|
||||
actions.verticalWithCtrl = MouseWheelAction::PAN_LEFT_RIGHT;
|
||||
actions.verticalWithShift = MouseWheelAction::PAN_UP_DOWN;
|
||||
actions.verticalWithAlt = MouseWheelAction::NONE;
|
||||
actions.horizontal = MouseWheelAction::NONE;
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::onZoomIn( wxCommandEvent& WXUNUSED( event ) )
|
||||
{
|
||||
ZoomIn( wxPoint( m_mouseMClick.x, m_mouseMClick.y ) );
|
||||
@@ -2188,6 +2082,199 @@ void mpWindow::OnPaint( wxPaintEvent& WXUNUSED( event ) )
|
||||
paintDC.Blit( 0, 0, m_scrX, m_scrY, targetDC, 0, 0 );
|
||||
}
|
||||
|
||||
void mpWindow::DoZoom( const wxPoint& centerPoint, double zoomFactor,
|
||||
wxOrientation directions )
|
||||
{
|
||||
if( m_yLocked )
|
||||
{
|
||||
if( directions == wxVERTICAL )
|
||||
return;
|
||||
directions = wxHORIZONTAL;
|
||||
}
|
||||
|
||||
const bool horizontally = (directions & wxHORIZONTAL) != 0;
|
||||
const bool vertically = (directions & wxVERTICAL) != 0;
|
||||
|
||||
pushZoomUndo( { m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax } );
|
||||
|
||||
// Preserve the position of the clicked point:
|
||||
wxPoint c( centerPoint );
|
||||
if( c == wxDefaultPosition )
|
||||
{
|
||||
GetClientSize( &m_scrX, &m_scrY );
|
||||
c.x = (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft;
|
||||
c.y = (m_scrY - m_marginTop - m_marginBottom) / 2 + m_marginTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
c.x = std::max( c.x, m_marginLeft );
|
||||
c.x = std::min( c.x, m_scrX - m_marginRight );
|
||||
c.y = std::max( c.y, m_marginTop );
|
||||
c.y = std::min( c.y, m_scrY - m_marginBottom );
|
||||
}
|
||||
|
||||
// Zoom in/out:
|
||||
const double MAX_SCALE = 1e6;
|
||||
const double newScaleX = horizontally ? (m_scaleX * zoomFactor) : m_scaleX;
|
||||
const double newScaleY = vertically ? (m_scaleY * zoomFactor) : m_scaleY;
|
||||
|
||||
// Baaaaad things happen when you zoom in too much..
|
||||
if( newScaleX > MAX_SCALE || newScaleY > MAX_SCALE )
|
||||
return;
|
||||
|
||||
if ( horizontally )
|
||||
{
|
||||
// Transform the clicked X point to layer coordinates:
|
||||
const double prior_layer_x = p2x( c.x );
|
||||
|
||||
// Adjust the new X scale and plot X origin:
|
||||
m_scaleX = newScaleX;
|
||||
m_posX = prior_layer_x - c.x / newScaleX;
|
||||
|
||||
// Recompute the desired X view extents:
|
||||
RecomputeDesiredX( m_desiredXmin, m_desiredXmax );
|
||||
}
|
||||
|
||||
if ( vertically )
|
||||
{
|
||||
// Transform the clicked Y point to layer coordinates:
|
||||
const double prior_layer_y = p2y( c.y );
|
||||
|
||||
// Adjust the new Y scale and plot Y origin:
|
||||
m_scaleY = newScaleY;
|
||||
m_posY = prior_layer_y + c.y / newScaleY;
|
||||
|
||||
// Recompute the desired Y view extents:
|
||||
RecomputeDesiredY( m_desiredYmin, m_desiredYmax );
|
||||
}
|
||||
|
||||
AdjustLimitedView( directions );
|
||||
|
||||
if (zoomFactor < 1.0)
|
||||
{
|
||||
// These additional checks are needed because AdjustLimitedView only adjusts the position
|
||||
// and not the scale.
|
||||
wxOrientation directionsNeedingRefitting = ViewNeedsRefitting( directions );
|
||||
|
||||
// If the view is still out-of-limits after AdjustLimitedView is called, perform a Fit
|
||||
// along the offending dimension(s).
|
||||
if( directionsNeedingRefitting != 0 )
|
||||
Fit( m_minX, m_maxX, m_minY, m_maxY, nullptr, nullptr, directionsNeedingRefitting );
|
||||
}
|
||||
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::RecomputeDesiredX( double& min, double& max )
|
||||
{
|
||||
const int plotScreenWidth = m_scrX - m_marginLeft - m_marginRight;
|
||||
const double plotSpanX = plotScreenWidth / m_scaleX;
|
||||
const double desiredSpanX = plotSpanX / ( 2*m_leftRightPlotGapFactor + 1 );
|
||||
const double xGap = desiredSpanX * m_leftRightPlotGapFactor;
|
||||
min = m_posX + ( m_marginLeft / m_scaleX ) + xGap;
|
||||
max = m_desiredXmin + desiredSpanX;
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::RecomputeDesiredY( double& min, double& max )
|
||||
{
|
||||
const int plotScreenHeight = m_scrY - m_marginTop - m_marginBottom;
|
||||
const double plotSpanY = plotScreenHeight / m_scaleY;
|
||||
const double desiredSpanY = plotSpanY / ( 2*m_topBottomPlotGapFactor + 1 );
|
||||
const double yGap = desiredSpanY * m_topBottomPlotGapFactor;
|
||||
max = m_posY - ( m_marginTop / m_scaleY) - yGap;
|
||||
min = m_desiredYmax - desiredSpanY;
|
||||
}
|
||||
|
||||
|
||||
wxOrientation mpWindow::ViewNeedsRefitting( wxOrientation directions ) const
|
||||
{
|
||||
if( !m_enableLimitedView )
|
||||
return static_cast<wxOrientation>( 0 );
|
||||
|
||||
// Allow a gap between the extrema of the curve and the edges of the plot area. Not to be
|
||||
// confused with the left/right/top/bottom margins outside the plot area.
|
||||
const double xGap = fabs( m_maxX - m_minX ) * m_leftRightPlotGapFactor;
|
||||
const double yGap = fabs( m_maxY - m_minY ) * m_topBottomPlotGapFactor;
|
||||
|
||||
wxOrientation result = {};
|
||||
|
||||
if ( (directions & wxHORIZONTAL) != 0 )
|
||||
{
|
||||
if ( ( m_desiredXmax > m_maxX + xGap ) || ( m_desiredXmin < m_minX - xGap ) )
|
||||
result = static_cast<wxOrientation>( result | wxHORIZONTAL );
|
||||
}
|
||||
|
||||
if ( (directions & wxVERTICAL) != 0 )
|
||||
{
|
||||
if ( ( m_desiredYmax > m_maxY + yGap ) || ( m_desiredYmin < m_minY - yGap ) )
|
||||
result = static_cast<wxOrientation>( result | wxVERTICAL );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void mpWindow::PerformMouseWheelAction( wxMouseEvent& event, MouseWheelAction action )
|
||||
{
|
||||
const int change = event.GetWheelRotation();
|
||||
const double changeUnitsX = change / m_scaleX;
|
||||
const double changeUnitsY = change / m_scaleY;
|
||||
const wxPoint clickPt( event.GetX(), event.GetY() );
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case MouseWheelAction::NONE:
|
||||
break;
|
||||
|
||||
case MouseWheelAction::PAN_LEFT_RIGHT:
|
||||
SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX,
|
||||
m_desiredXmin + changeUnitsX );
|
||||
UpdateAll();
|
||||
break;
|
||||
|
||||
case MouseWheelAction::PAN_RIGHT_LEFT:
|
||||
SetXView( m_posX - changeUnitsX, m_desiredXmax - changeUnitsX,
|
||||
m_desiredXmin - changeUnitsX );
|
||||
UpdateAll();
|
||||
break;
|
||||
|
||||
case MouseWheelAction::PAN_UP_DOWN:
|
||||
if( !m_yLocked )
|
||||
{
|
||||
SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY,
|
||||
m_desiredYmin + changeUnitsY );
|
||||
UpdateAll();
|
||||
}
|
||||
break;
|
||||
|
||||
case MouseWheelAction::ZOOM:
|
||||
if( event.GetWheelRotation() > 0 )
|
||||
ZoomIn( clickPt );
|
||||
else
|
||||
ZoomOut( clickPt );
|
||||
break;
|
||||
|
||||
case MouseWheelAction::ZOOM_HORIZONTALLY:
|
||||
if( event.GetWheelRotation() > 0 )
|
||||
ZoomIn( clickPt, zoomIncrementalFactor, wxHORIZONTAL );
|
||||
else
|
||||
ZoomOut( clickPt, zoomIncrementalFactor, wxHORIZONTAL );
|
||||
break;
|
||||
|
||||
case MouseWheelAction::ZOOM_VERTICALLY:
|
||||
if( event.GetWheelRotation() > 0 )
|
||||
ZoomIn( clickPt, zoomIncrementalFactor, wxVERTICAL );
|
||||
else
|
||||
ZoomOut( clickPt, zoomIncrementalFactor, wxVERTICAL );
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool mpWindow::UpdateBBox()
|
||||
{
|
||||
@@ -2392,6 +2479,59 @@ void mpWindow::SetColourTheme( const wxColour& bgColour, const wxColour& drawCol
|
||||
}
|
||||
|
||||
|
||||
template <typename... Ts>
|
||||
mpWindow::mpWindow( DelegatingContructorTag, Ts&&... windowArgs ) :
|
||||
wxWindow( std::forward<Ts>( windowArgs )... ),
|
||||
m_minX( 0.0 ),
|
||||
m_maxX( 0.0 ),
|
||||
m_minY( 0.0 ),
|
||||
m_maxY( 0.0 ),
|
||||
m_scaleX( 1.0 ),
|
||||
m_scaleY( 1.0 ),
|
||||
m_posX( 0.0 ),
|
||||
m_posY( 0.0 ),
|
||||
m_scrX( 64 ),
|
||||
m_scrY( 64 ),
|
||||
m_clickedX( 0 ),
|
||||
m_clickedY( 0 ),
|
||||
m_yLocked( false ),
|
||||
m_desiredXmin( 0.0 ),
|
||||
m_desiredXmax( 1.0 ),
|
||||
m_desiredYmin( 0.0 ),
|
||||
m_desiredYmax( 1.0 ),
|
||||
m_topBottomPlotGapFactor( 0.03 ),
|
||||
m_leftRightPlotGapFactor( 0.0 ),
|
||||
m_marginTop( 0 ),
|
||||
m_marginRight( 0 ),
|
||||
m_marginBottom( 0 ),
|
||||
m_marginLeft( 0 ),
|
||||
m_last_lx( 0 ),
|
||||
m_last_ly( 0 ),
|
||||
m_buff_bmp( nullptr ),
|
||||
m_enableDoubleBuffer( false ),
|
||||
m_enableMouseNavigation( true ),
|
||||
m_enableLimitedView( false ),
|
||||
m_mouseWheelActions( defaultMouseWheelActions() ),
|
||||
m_movingInfoLayer( nullptr ),
|
||||
m_zooming( false )
|
||||
{}
|
||||
|
||||
|
||||
void mpWindow::initializeGraphicsContext()
|
||||
{
|
||||
if( wxGraphicsContext* ctx = m_buff_dc.GetGraphicsContext() )
|
||||
{
|
||||
if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST )
|
||||
|| !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) )
|
||||
{
|
||||
ctx->SetInterpolationQuality( wxINTERPOLATION_FAST );
|
||||
}
|
||||
|
||||
ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// mpFXYVector implementation - by Jose Luis Blanco (AGO-2007)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user