kicad-developers team mailing list archive
-
kicad-developers team
-
Mailing list archive
-
Message #38437
Re: [PATCH] GTK+3 zooming
Hi Wayne, Seth,
Here is the patchset with:
* A default ctor param and get/setters
* Trace mask in the header. I somehow haven't noticed this file before, sorry!
Cheers,
John
On Sun, Nov 25, 2018 at 9:35 PM Wayne Stambaugh <stambaughw@xxxxxxxxx> wrote:
>
> Hey John,
>
> On 11/23/2018 8:26 AM, John Beard wrote:
> > Hi,
> >
> > This is a patch to refactor the zooming of WX_VIEW_CONTROL. This is
> > related to lp:1786515 [1], but it's not a fix, it's just a refactor to
> > help debug the problem, and also tidy the code.
> >
> > The refactor is done to avoid big chunks of conditionally compiled
> > code kicking around in functions, and to make the platform behaviours
> > clearer. The platform-specific bit is still ifdeff'ed, as making that
> > run-time is a little bit more verbose than is required, I feel.
> >
> > There's also a patch (#2) for the libcommon tests to init WX.This
> > means you can see WXTRACE messages. The teardown of WX prints some
> > glib stuff on GTK3 [2], but it should be harmless, and doing the
> > teardown keeps valgrind happier.
> >
> > Cheers,
> >
> > John
> >
> > [1]: https://bugs.launchpad.net/kicad/+bug/1786515
> > [2]: http://trac.wxwidgets.org/ticket/18274
> >
>
> I tested your patches on windows and it looks good to me. Along with
> Seth's suggestions, I would like to ask you to please define the
> wxLogTrace flag string "SCROLL_ZOOM" in include/trace_helpers.h and
> common/trace_helpers.cpp so they will be added to the "Trace Environment
> Variables" section in the developer docs[1].
>
> Cheers,
>
> Wayne
>
> [1]: http://docs.kicad-pcb.org/doxygen/group__trace__env__vars.html
>
>
> _______________________________________________
> Mailing list: https://launchpad.net/~kicad-developers
> Post to : kicad-developers@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~kicad-developers
> More help : https://help.launchpad.net/ListHelp
From 317bb05511f079024de78058c72e8e3b974c70bd Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Fri, 23 Nov 2018 10:26:19 +0000
Subject: [PATCH 2/4] QA: Initialise WX for the libcommon tests
If this is not done, things like logging and trace don't work,
as they need WX to be set up first.
---
qa/common/test_module.cpp | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/qa/common/test_module.cpp b/qa/common/test_module.cpp
index 38f18fd03..36183b2db 100644
--- a/qa/common/test_module.cpp
+++ b/qa/common/test_module.cpp
@@ -22,11 +22,27 @@
*/
/**
- * Main file for the geometry tests to be compiled
+ * Main file for the libcommon tests to be compiled
*/
+#include <boost/test/unit_test.hpp>
-#define BOOST_TEST_MAIN
-#define BOOST_TEST_MODULE "Common library module tests"
+#include <wx/init.h>
-#include <boost/test/unit_test.hpp>
+bool init_unit_test()
+{
+ boost::unit_test::framework::master_test_suite().p_name.value = "Common library module tests";
+ return wxInitialize();
+}
+
+
+int main( int argc, char* argv[] )
+{
+ int ret = boost::unit_test::unit_test_main( &init_unit_test, argc, argv );
+
+ // This causes some glib warnings on GTK3 (http://trac.wxwidgets.org/ticket/18274)
+ // but without it, Valgrind notices a lot of leaks from WX
+ wxUninitialize();
+
+ return ret;
+}
\ No newline at end of file
--
2.19.2
From 01cc5b1cbed9c57c388ada79c7781f42df1f9654 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Thu, 22 Nov 2018 16:24:17 +0000
Subject: [PATCH 1/4] Break zoom control into a self-contained controller
This is done to avoid a big chunk of conditionally-compiled code
in the middle of the event function.
Also separates the zoom logic from the WX_VIEW_CONTROLS object
and isolates it in a separate class behind a clearer interface.
Add some simple tests for sane steps on GTK+3-sized scroll
steps.
---
Documentation/development/testing.md | 1 +
common/CMakeLists.txt | 1 +
common/trace_helpers.cpp | 6 +-
common/view/wx_view_controls.cpp | 65 +++++---------
common/view/zoom_controller.cpp | 112 ++++++++++++++++++++++++
include/trace_helpers.h | 11 ++-
include/view/wx_view_controls.h | 14 +--
include/view/zoom_controller.h | 110 +++++++++++++++++++++++
qa/common/CMakeLists.txt | 2 +
qa/common/view/test_zoom_controller.cpp | 90 +++++++++++++++++++
10 files changed, 360 insertions(+), 52 deletions(-)
create mode 100644 common/view/zoom_controller.cpp
create mode 100644 include/view/zoom_controller.h
create mode 100644 qa/common/view/test_zoom_controller.cpp
diff --git a/Documentation/development/testing.md b/Documentation/development/testing.md
index 81fd22701..7fddb2050 100644
--- a/Documentation/development/testing.md
+++ b/Documentation/development/testing.md
@@ -243,6 +243,7 @@ Some available masks:
* `GAL_CACHED_CONTAINER`
* `PNS`
* `CN`
+ * `SCROLL_ZOOM` - for the scroll-wheel zooming logic in GAL
* Plugin-specific (including "standard" KiCad formats):
* `3D_CACHE`
* `3D_SG`
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 924599753..7c1db83df 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -46,6 +46,7 @@ set( GAL_SRCS
view/view_controls.cpp
view/view_overlay.cpp
view/wx_view_controls.cpp
+ view/zoom_controller.cpp
# OpenGL GAL
gal/opengl/opengl_gal.cpp
diff --git a/common/trace_helpers.cpp b/common/trace_helpers.cpp
index 8586ef356..b9d084013 100644
--- a/common/trace_helpers.cpp
+++ b/common/trace_helpers.cpp
@@ -27,11 +27,6 @@
* @brief wxLogTrace helper implementation.
*/
-#include <wx/defs.h>
-#include <wx/string.h>
-#include <wx/event.h>
-#include <wx/arrstr.h>
-
#include <trace_helpers.h>
@@ -48,6 +43,7 @@ const wxChar* const traceAutoSave = wxT( "KICAD_AUTOSAVE" );
const wxChar* const tracePathsAndFiles = wxT( "KICAD_PATHS_AND_FILES" );
const wxChar* const traceLocale = wxT( "KICAD_LOCALE" );
const wxChar* const traceScreen = wxT( "KICAD_SCREEN" );
+const wxChar* const traceZoomScroll = wxT( "KICAD_ZOOM_SCROLL" );
wxString dump( const wxArrayString& aArray )
diff --git a/common/view/wx_view_controls.cpp b/common/view/wx_view_controls.cpp
index 81e0e5752..6ef378469 100644
--- a/common/view/wx_view_controls.cpp
+++ b/common/view/wx_view_controls.cpp
@@ -28,6 +28,7 @@
#include <view/view.h>
#include <view/wx_view_controls.h>
+#include <view/zoom_controller.h>
#include <gal/graphics_abstraction_layer.h>
#include <tool/tool_dispatcher.h>
@@ -35,6 +36,20 @@ using namespace KIGFX;
const wxEventType WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE = wxNewEventType();
+
+static std::unique_ptr<ZOOM_CONTROLLER> GetZoomControllerForPlatform()
+{
+#ifdef __WXMAC__
+ // On Apple pointer devices, wheel events occur frequently and with
+ // smaller rotation values. For those devices, let's handle zoom
+ // based on the rotation amount rather than the time difference.
+ return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MAC_SCALE );
+#else
+ return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>( 500 );
+#endif
+}
+
+
WX_VIEW_CONTROLS::WX_VIEW_CONTROLS( VIEW* aView, wxScrolledCanvas* aParentPanel ) :
VIEW_CONTROLS( aView ), m_state( IDLE ), m_parentPanel( aParentPanel ),
m_scrollScale( 1.0, 1.0 ), m_cursorPos( 0, 0 ), m_updateCursor( true )
@@ -72,12 +87,19 @@ WX_VIEW_CONTROLS::WX_VIEW_CONTROLS( VIEW* aView, wxScrolledCanvas* aParentPanel
m_parentPanel->Connect( wxEVT_SCROLLWIN_PAGEDOWN,
wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), NULL, this );
+ m_zoomController = GetZoomControllerForPlatform();
+
m_panTimer.SetOwner( this );
this->Connect( wxEVT_TIMER,
wxTimerEventHandler( WX_VIEW_CONTROLS::onTimer ), NULL, this );
}
+WX_VIEW_CONTROLS::~WX_VIEW_CONTROLS()
+{
+}
+
+
void WX_VIEW_CONTROLS::onMotion( wxMouseEvent& aEvent )
{
bool isAutoPanning = false;
@@ -110,7 +132,6 @@ void WX_VIEW_CONTROLS::onMotion( wxMouseEvent& aEvent )
void WX_VIEW_CONTROLS::onWheel( wxMouseEvent& aEvent )
{
const double wheelPanSpeed = 0.001;
- const double zoomLevelScale = 1.2; // The minimal step value when changing the current zoom level
// mousewheelpan disabled:
// wheel + ctrl -> horizontal scrolling;
@@ -153,46 +174,8 @@ void WX_VIEW_CONTROLS::onWheel( wxMouseEvent& aEvent )
}
else
{
- // Zooming
- wxLongLong timeStamp = wxGetLocalTimeMillis();
- double timeDiff = timeStamp.ToDouble() - m_timeStamp.ToDouble();
- int rotation = aEvent.GetWheelRotation();
- double zoomScale = 1.0;
-
-#ifdef __WXMAC__
- // On Apple pointer devices, wheel events occur frequently and with
- // smaller rotation values. For those devices, let's handle zoom
- // based on the rotation amount rather than the time difference.
-
- // Unused
- ( void )timeDiff;
-
- rotation = ( rotation > 0 ) ? std::min( rotation , 100 )
- : std::max( rotation , -100 );
-
- double dscale = rotation * 0.01;
- zoomScale = ( rotation > 0 ) ? (1 + dscale) : 1/(1 - dscale);
-
-#else
-
- m_timeStamp = timeStamp;
-
- // Set scaling speed depending on scroll wheel event interval
- if( timeDiff < 500 && timeDiff > 0 )
- {
- zoomScale = 2.05 - timeDiff / 500;
-
- // be sure zoomScale value is significant
- zoomScale = std::max( zoomScale, zoomLevelScale );
-
- if( rotation < 0 )
- zoomScale = 1.0 / zoomScale;
- }
- else
- {
- zoomScale = ( rotation > 0 ) ? zoomLevelScale : 1/zoomLevelScale;
- }
-#endif
+ int rotation = aEvent.GetWheelRotation();
+ double zoomScale = m_zoomController->GetScaleForRotation( rotation );
if( IsCursorWarpingEnabled() )
{
diff --git a/common/view/zoom_controller.cpp b/common/view/zoom_controller.cpp
new file mode 100644
index 000000000..9a426cfad
--- /dev/null
+++ b/common/view/zoom_controller.cpp
@@ -0,0 +1,112 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
+ * Copyright (C) 2013-2015 CERN
+ * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Tomasz Wlostowski <tomasz.wlostowski@xxxxxxx>
+ * @author Maciej Suminski <maciej.suminski@xxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <view/zoom_controller.h>
+
+#include <trace_helpers.h>
+
+#include <wx/log.h>
+#include <wx/time.h> // For timestamping events
+
+#include <algorithm>
+
+using namespace KIGFX;
+
+
+ACCELERATING_ZOOM_CONTROLLER::ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout )
+ : m_lastTimeStamp( getTimeStamp() ), m_accTimeout( aAccTimeout )
+{
+}
+
+
+double ACCELERATING_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
+{
+ // The minimal step value when changing the current zoom level
+ const double zoomLevelScale = 1.2;
+
+ const auto timeStamp = getTimeStamp();
+ auto timeDiff = timeStamp - m_lastTimeStamp;
+
+ m_lastTimeStamp = timeStamp;
+
+ wxLogTrace( traceZoomScroll,
+ wxString::Format( "Rot %d, time diff: %ldms", aRotation, timeDiff ) );
+
+ double zoomScale;
+
+ // Set scaling speed depending on scroll wheel event interval
+ if( timeDiff < m_accTimeout && timeDiff > 0 )
+ {
+ zoomScale = 2.05 - timeDiff / m_accTimeout;
+
+ // be sure zoomScale value is significant
+ zoomScale = std::max( zoomScale, zoomLevelScale );
+
+ if( aRotation < 0 )
+ zoomScale = 1.0 / zoomScale;
+ }
+ else
+ {
+ zoomScale = ( aRotation > 0 ) ? zoomLevelScale : 1 / zoomLevelScale;
+ }
+
+ wxLogTrace( traceZoomScroll, wxString::Format( " Zoom factor: %f", zoomScale ) );
+
+ return zoomScale;
+}
+
+
+double ACCELERATING_ZOOM_CONTROLLER::getTimeStamp() const
+{
+ return wxGetLocalTimeMillis().ToDouble();
+}
+
+
+CONSTANT_ZOOM_CONTROLLER::CONSTANT_ZOOM_CONTROLLER( double aScale ) : m_scale( aScale )
+{
+}
+
+
+double CONSTANT_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
+{
+ wxLogTrace( traceZoomScroll, wxString::Format( "Rot %d", aRotation ) );
+
+ aRotation = ( aRotation > 0 ) ? std::min( aRotation, 100 ) : std::max( aRotation, -100 );
+
+ double dscale = aRotation * m_scale;
+
+ double zoom_scale = ( aRotation > 0 ) ? ( 1 + dscale ) : 1 / ( 1 - dscale );
+
+ wxLogTrace( traceZoomScroll, wxString::Format( " Zoom factor: %f", zoom_scale ) );
+
+ return zoom_scale;
+}
+
+// need these until C++17
+constexpr double CONSTANT_ZOOM_CONTROLLER::MAC_SCALE;
+constexpr double CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE;
\ No newline at end of file
diff --git a/include/trace_helpers.h b/include/trace_helpers.h
index 115d87d80..050ab1daf 100644
--- a/include/trace_helpers.h
+++ b/include/trace_helpers.h
@@ -30,6 +30,10 @@
#ifndef _TRACE_HELPERS_H_
#define _TRACE_HELPERS_H_
+#include <wx/arrstr.h>
+#include <wx/event.h>
+#include <wx/string.h>
+
/**
* @defgroup trace_env_vars Trace Environment Variables
*
@@ -58,7 +62,6 @@ extern const wxChar* const traceFindReplace;
*/
extern const wxChar* const kicadTraceCoords;
-
/**
* Flag to enable wxKeyEvent debug tracing.
*/
@@ -109,6 +112,12 @@ extern const wxChar* const traceLocale;
*/
extern const wxChar* const traceScreen;
+/**
+ * Flag to enable debug output of zoom-scrolling calculations in
+ * #KIGFX::ZOOM_CONTROLER and derivatives.
+ */
+extern const wxChar* const traceZoomScroll;
+
///@}
/**
diff --git a/include/view/wx_view_controls.h b/include/view/wx_view_controls.h
index b71d4b1ea..d3b7d0733 100644
--- a/include/view/wx_view_controls.h
+++ b/include/view/wx_view_controls.h
@@ -35,10 +35,15 @@
#include <view/view_controls.h>
+#include <memory>
+
class EDA_DRAW_PANEL_GAL;
namespace KIGFX
{
+
+class ZOOM_CONTROLLER;
+
/**
* Class WX_VIEW_CONTROLS
* is a specific implementation of class VIEW_CONTROLS for wxWidgets library.
@@ -47,8 +52,7 @@ class WX_VIEW_CONTROLS : public VIEW_CONTROLS, public wxEvtHandler
{
public:
WX_VIEW_CONTROLS( VIEW* aView, wxScrolledCanvas* aParentPanel );
- virtual ~WX_VIEW_CONTROLS()
- {}
+ virtual ~WX_VIEW_CONTROLS();
/// Handler functions
void onWheel( wxMouseEvent& aEvent );
@@ -146,9 +150,6 @@ private:
/// Current direction of panning (only autopanning mode)
VECTOR2D m_panDirection;
- /// Used for determining time intervals between scroll & zoom events
- wxLongLong m_timeStamp;
-
/// Timer repsonsible for handling autopanning
wxTimer m_panTimer;
@@ -163,6 +164,9 @@ private:
/// Flag deciding whether the cursor position should be calculated using the mouse position
bool m_updateCursor;
+
+ /// a ZOOM_CONTROLLER that determines zoom steps. This is platform-specific.
+ std::unique_ptr<ZOOM_CONTROLLER> m_zoomController;
};
} // namespace KIGFX
diff --git a/include/view/zoom_controller.h b/include/view/zoom_controller.h
new file mode 100644
index 000000000..a367c10bf
--- /dev/null
+++ b/include/view/zoom_controller.h
@@ -0,0 +1,110 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013-2018 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ */
+
+/**
+ * @file zoom_control.h
+ * @brief ZOOM_CONTROLLER class definition.
+ */
+
+#ifndef __ZOOM_CONTROLLER_H
+#define __ZOOM_CONTROLLER_H
+
+
+namespace KIGFX
+{
+
+/**
+ * Class that handles the response of the zoom scale to external inputs
+ */
+class ZOOM_CONTROLLER
+{
+public:
+ virtual ~ZOOM_CONTROLLER() = default;
+
+ /**
+ * Gets the scale factor produced by a given mousewheel rotation
+ * @param aRotation rotation of the mouse wheel (this comes from
+ * wxMouseEvent::GetWheelRotation())
+ * @return the scale factor to scroll by
+ */
+ virtual double GetScaleForRotation( int aRotation ) = 0;
+};
+
+
+/**
+ * Class that zooms faster if scroll events happen very close together.
+ */
+class ACCELERATING_ZOOM_CONTROLLER : public ZOOM_CONTROLLER
+{
+public:
+ /**
+ * @param aAccTimeout the timeout - if a scoll happens within this timeframe,
+ * the zoom will be faster
+ */
+ ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout );
+
+ double GetScaleForRotation( int aRotation ) override;
+
+private:
+ /**
+ * @return the timestamp of an event at the current time. Monotonic.
+ */
+ double getTimeStamp() const;
+
+ /// The timestamp of the last event
+ double m_lastTimeStamp;
+ /// The timeout value
+ unsigned m_accTimeout;
+};
+
+
+/**
+ * A ZOOM_CONTROLLER that zooms by a fixed factor based only on the magnitude
+ * of the scroll wheel rotation.
+ */
+class CONSTANT_ZOOM_CONTROLLER : public ZOOM_CONTROLLER
+{
+public:
+ /**
+ * @param aScale a scaling parameter that adjusts the magnitude of the
+ * scroll. This factor might be dependent on the platform for comfort.
+ */
+ CONSTANT_ZOOM_CONTROLLER( double aScale );
+
+ double GetScaleForRotation( int aRotation ) override;
+
+ /// A suitable (magic) scale factor for GTK3 systems
+ static constexpr double GTK3_SCALE = 0.001;
+
+ /// A suitable (magic) scale factor for Mac systems
+ static constexpr double MAC_SCALE = 0.01;
+
+private:
+ /// The scale factor set by the constructor.
+ double m_scale;
+};
+
+} // namespace KIGFX
+
+#endif
diff --git a/qa/common/CMakeLists.txt b/qa/common/CMakeLists.txt
index f6a7298cc..20979fb46 100644
--- a/qa/common/CMakeLists.txt
+++ b/qa/common/CMakeLists.txt
@@ -41,6 +41,8 @@ add_executable( qa_common
test_utf8.cpp
geometry/test_fillet.cpp
+
+ view/test_zoom_controller.cpp
)
include_directories(
diff --git a/qa/common/view/test_zoom_controller.cpp b/qa/common/view/test_zoom_controller.cpp
new file mode 100644
index 000000000..a1f7b209e
--- /dev/null
+++ b/qa/common/view/test_zoom_controller.cpp
@@ -0,0 +1,90 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <boost/test/test_case_template.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include <view/zoom_controller.h>
+
+
+// All these tests are of a class in KIGFX
+using namespace KIGFX;
+
+
+/**
+ * Declares a struct as the Boost test fixture.
+ */
+BOOST_AUTO_TEST_SUITE( ZoomController )
+
+
+struct CONST_ZOOM_CASE
+{
+ double scale_factor;
+ int scroll_amount;
+ double exp_zoom_in;
+ double exp_zoom_out;
+};
+
+
+/*
+ * Some "sane" examples for steps, scale factors and results.
+ * These should be actual examples that could be encountered
+ *
+ * TODO: Add more cases for, eg, Mac
+ */
+static const std::vector<CONST_ZOOM_CASE> const_zoom_cases = {
+ // A single scroll step on a GTK3 Linux system
+ // 120 is the standard wheel delta, so it's what you might expect
+ // from a single scroll wheel detent.
+ { CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE, 120, 1.1, 1 / 1.1 },
+};
+
+/**
+ * Check basic setting and getting of values
+ */
+BOOST_AUTO_TEST_CASE( ConstController )
+{
+ // How close we need to be (not very, this is a subjective thing anyway)
+ const double tol_percent = 10;
+
+ double scale_for_step;
+
+ for( const auto& c : const_zoom_cases )
+ {
+ CONSTANT_ZOOM_CONTROLLER zoom_ctrl( c.scale_factor );
+
+ scale_for_step = zoom_ctrl.GetScaleForRotation( c.scroll_amount );
+ BOOST_CHECK_CLOSE( scale_for_step, c.exp_zoom_in, tol_percent );
+
+ scale_for_step = zoom_ctrl.GetScaleForRotation( -c.scroll_amount );
+ BOOST_CHECK_CLOSE( scale_for_step, c.exp_zoom_out, tol_percent );
+ }
+}
+
+/*
+ * Testing the accelerated version without making a very slow test is a little
+ * tricky and would need a mock timestamping interface, which complicates the
+ * real interface a bit and does not really seem worth the effort.
+ */
+
+BOOST_AUTO_TEST_SUITE_END()
--
2.19.2
From 0aa78a5e958b7b32218cfb588c4a365bbe2b53ec Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Thu, 22 Nov 2018 17:01:59 +0000
Subject: [PATCH 3/4] Zoom: Use std::chrono for the timestamping
The reduces a little bit of WX dependency, and makes
the timing code a bit more type-safe.
Also adds a more testable interface for the accelerated
zoom controller.
---
common/view/wx_view_controls.cpp | 2 +-
common/view/zoom_controller.cpp | 50 ++++++++++++-----
include/view/zoom_controller.h | 59 +++++++++++++++++---
qa/common/view/test_zoom_controller.cpp | 74 ++++++++++++++++++++++---
4 files changed, 155 insertions(+), 30 deletions(-)
diff --git a/common/view/wx_view_controls.cpp b/common/view/wx_view_controls.cpp
index 6ef378469..7ed899165 100644
--- a/common/view/wx_view_controls.cpp
+++ b/common/view/wx_view_controls.cpp
@@ -45,7 +45,7 @@ static std::unique_ptr<ZOOM_CONTROLLER> GetZoomControllerForPlatform()
// based on the rotation amount rather than the time difference.
return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MAC_SCALE );
#else
- return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>( 500 );
+ return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>();
#endif
}
diff --git a/common/view/zoom_controller.cpp b/common/view/zoom_controller.cpp
index 9a426cfad..2d01fa695 100644
--- a/common/view/zoom_controller.cpp
+++ b/common/view/zoom_controller.cpp
@@ -28,19 +28,45 @@
#include <view/zoom_controller.h>
+#include <make_unique.h>
#include <trace_helpers.h>
#include <wx/log.h>
-#include <wx/time.h> // For timestamping events
#include <algorithm>
using namespace KIGFX;
-ACCELERATING_ZOOM_CONTROLLER::ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout )
- : m_lastTimeStamp( getTimeStamp() ), m_accTimeout( aAccTimeout )
+/**
+ * A very simple timestamper that uses the #KIGFX::ACCELERATING_ZOOM_CONTROLLER::CLOCK
+ * to provide a timestamp. Since that's a steady_clock, it's monotonic.
+ */
+class SIMPLE_TIMESTAMPER : public ACCELERATING_ZOOM_CONTROLLER::TIMESTAMP_PROVIDER
{
+public:
+ ACCELERATING_ZOOM_CONTROLLER::TIME_PT GetTimestamp() override
+ {
+ return ACCELERATING_ZOOM_CONTROLLER::CLOCK::now();
+ }
+};
+
+
+ACCELERATING_ZOOM_CONTROLLER::ACCELERATING_ZOOM_CONTROLLER(
+ const TIMEOUT& aAccTimeout, TIMESTAMP_PROVIDER* aTimestampProv )
+ : m_accTimeout( aAccTimeout )
+{
+ if( aTimestampProv )
+ {
+ m_timestampProv = aTimestampProv;
+ }
+ else
+ {
+ m_ownTimestampProv = std::make_unique<SIMPLE_TIMESTAMPER>();
+ m_timestampProv = m_ownTimestampProv.get();
+ }
+
+ m_lastTimestamp = m_timestampProv->GetTimestamp();
}
@@ -49,18 +75,18 @@ double ACCELERATING_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
// The minimal step value when changing the current zoom level
const double zoomLevelScale = 1.2;
- const auto timeStamp = getTimeStamp();
- auto timeDiff = timeStamp - m_lastTimeStamp;
+ const auto timestamp = m_timestampProv->GetTimestamp();
+ auto timeDiff = std::chrono::duration_cast<TIMEOUT>( timestamp - m_lastTimestamp );
- m_lastTimeStamp = timeStamp;
+ m_lastTimestamp = timestamp;
wxLogTrace( traceZoomScroll,
- wxString::Format( "Rot %d, time diff: %ldms", aRotation, timeDiff ) );
+ wxString::Format( "Rot %d, time diff: %ldms", aRotation, timeDiff.count() ) );
double zoomScale;
// Set scaling speed depending on scroll wheel event interval
- if( timeDiff < m_accTimeout && timeDiff > 0 )
+ if( timeDiff < m_accTimeout )
{
zoomScale = 2.05 - timeDiff / m_accTimeout;
@@ -81,12 +107,6 @@ double ACCELERATING_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
}
-double ACCELERATING_ZOOM_CONTROLLER::getTimeStamp() const
-{
- return wxGetLocalTimeMillis().ToDouble();
-}
-
-
CONSTANT_ZOOM_CONTROLLER::CONSTANT_ZOOM_CONTROLLER( double aScale ) : m_scale( aScale )
{
}
@@ -108,5 +128,7 @@ double CONSTANT_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
}
// need these until C++17
+constexpr ACCELERATING_ZOOM_CONTROLLER::TIMEOUT ACCELERATING_ZOOM_CONTROLLER::DEFAULT_TIMEOUT;
+
constexpr double CONSTANT_ZOOM_CONTROLLER::MAC_SCALE;
constexpr double CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE;
\ No newline at end of file
diff --git a/include/view/zoom_controller.h b/include/view/zoom_controller.h
index a367c10bf..fa2eed6ab 100644
--- a/include/view/zoom_controller.h
+++ b/include/view/zoom_controller.h
@@ -30,6 +30,8 @@
#ifndef __ZOOM_CONTROLLER_H
#define __ZOOM_CONTROLLER_H
+#include <chrono>
+#include <memory>
namespace KIGFX
{
@@ -58,24 +60,65 @@ public:
class ACCELERATING_ZOOM_CONTROLLER : public ZOOM_CONTROLLER
{
public:
+ /// The type of the acceleration timeout
+ using TIMEOUT = std::chrono::milliseconds;
+
+ /// The clock used for the timestamp (guaranteed to be monotonic)
+ using CLOCK = std::chrono::steady_clock;
+
+ /// The type of the time stamps
+ using TIME_PT = std::chrono::time_point<CLOCK>;
+
+ /// The default timeout, after which a another scroll will not be accelerated
+ static constexpr TIMEOUT DEFAULT_TIMEOUT = std::chrono::milliseconds( 500 );
+
+ /*
+ * A class interface that provides timestamps for events
+ */
+ class TIMESTAMP_PROVIDER
+ {
+ public:
+ virtual ~TIMESTAMP_PROVIDER() = default;
+
+ /*
+ * @return the timestamp at the current time
+ */
+ virtual TIME_PT GetTimestamp() = 0;
+ };
+
/**
- * @param aAccTimeout the timeout - if a scoll happens within this timeframe,
+ * @param aAccTimeout the timeout - if a scroll happens within this timeframe,
* the zoom will be faster
+ * @param aTimestampProv a provider for timestamps. If null, a default will
+ * be provided, which is the main steady_clock (this is probably what you
+ * want for real usage)
*/
- ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout );
+ ACCELERATING_ZOOM_CONTROLLER( const TIMEOUT& aAccTimeout = DEFAULT_TIMEOUT,
+ TIMESTAMP_PROVIDER* aTimestampProv = nullptr );
double GetScaleForRotation( int aRotation ) override;
+ TIMEOUT GetTimeout() const
+ {
+ return m_accTimeout;
+ }
+
+ void SetTimeout( const TIMEOUT& aNewTimeout )
+ {
+ m_accTimeout = aNewTimeout;
+ }
+
private:
- /**
- * @return the timestamp of an event at the current time. Monotonic.
- */
- double getTimeStamp() const;
+ /// The timestamp provider to use (might be provided externally)
+ TIMESTAMP_PROVIDER* m_timestampProv;
+
+ /// Any provider owned by this class (the default one, if used)
+ std::unique_ptr<TIMESTAMP_PROVIDER> m_ownTimestampProv;
/// The timestamp of the last event
- double m_lastTimeStamp;
+ TIME_PT m_lastTimestamp;
/// The timeout value
- unsigned m_accTimeout;
+ TIMEOUT m_accTimeout;
};
diff --git a/qa/common/view/test_zoom_controller.cpp b/qa/common/view/test_zoom_controller.cpp
index a1f7b209e..ff89f0e43 100644
--- a/qa/common/view/test_zoom_controller.cpp
+++ b/qa/common/view/test_zoom_controller.cpp
@@ -31,9 +31,6 @@
using namespace KIGFX;
-/**
- * Declares a struct as the Boost test fixture.
- */
BOOST_AUTO_TEST_SUITE( ZoomController )
@@ -81,10 +78,73 @@ BOOST_AUTO_TEST_CASE( ConstController )
}
}
-/*
- * Testing the accelerated version without making a very slow test is a little
- * tricky and would need a mock timestamping interface, which complicates the
- * real interface a bit and does not really seem worth the effort.
+/**
+ * Timestamper that returns predefined values from a vector
+ */
+class PREDEF_TIMESTAMPER : public ACCELERATING_ZOOM_CONTROLLER::TIMESTAMP_PROVIDER
+{
+public:
+ using STAMP_LIST = std::vector<int>;
+
+ PREDEF_TIMESTAMPER( const STAMP_LIST& aStamps )
+ : m_stamps( aStamps ), m_iter( m_stamps.begin() )
+ {
+ }
+
+ /**
+ * @return the next time point in the predefined sequence
+ */
+ ACCELERATING_ZOOM_CONTROLLER::TIME_PT GetTimestamp() override
+ {
+ // Don't ask for more samples than given
+ BOOST_REQUIRE( m_iter != m_stamps.end() );
+
+ return ACCELERATING_ZOOM_CONTROLLER::TIME_PT( std::chrono::milliseconds( *m_iter++ ) );
+ }
+
+ const STAMP_LIST m_stamps;
+ STAMP_LIST::const_iterator m_iter;
+};
+
+
+struct ACCEL_ZOOM_CASE
+{
+ int timeout;
+ std::vector<int> stamps; // NB includes the initial stamp!
+ std::vector<int> scrolls;
+ std::vector<double> zooms;
+};
+
+static const std::vector<ACCEL_ZOOM_CASE> accel_cases = {
+ // Scrolls widely spaced, just go up and down by a constant factor
+ { 500, { 0, 1000, 2000, 3000 }, { 120, 120, -120 }, { 1.2, 1.2, 1 / 1.2 } },
+ // Close scrolls - acceleration on the latter
+ { 500, { 0, 1000, 1100 }, { 120, 120 }, { 1.2, 2.05 } },
+};
+
+
+/**
+ * Check basic setting and getting of values
*/
+BOOST_AUTO_TEST_CASE( AccelController )
+{
+ const double tol_percent = 10.0;
+
+ for( const auto& c : accel_cases )
+ {
+ PREDEF_TIMESTAMPER timestamper( c.stamps );
+
+ ACCELERATING_ZOOM_CONTROLLER zoom_ctrl(
+ std::chrono::milliseconds( c.timeout ), ×tamper );
+
+ for( unsigned i = 0; i < c.scrolls.size(); i++ )
+ {
+ const auto zoom_scale = zoom_ctrl.GetScaleForRotation( c.scrolls[i] );
+
+ BOOST_CHECK_CLOSE( zoom_scale, c.zooms[i], tol_percent );
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
--
2.19.2
Follow ups
References