← Back to team overview

kicad-developers team mailing list archive

[PATCH] Measurement tool for GAL

 

Hi,

Here's a tool I'm had on my list for a while - a ruler tool.

This patch set introduces a new directory, for "preview items" which
are EDA_ITEMS that are only used for transient previews, such as
drawing aids and selection boxes. These are located in common, so that
they can (in theory) be used in any GAL canvas, not just
Pcbnew/Modedit.

There are also some functions that hopefully will be useful for other
preview items in future in the utils file.

There's an oustanding bug with the OpenGL GAL in that it cannot draw
spaces in text, so there are a couple of places where it pre-emptively
strips spaces that should be removed when that bug is fixed.
(https://bugs.launchpad.net/kicad/+bug/1668455).

Cheers,

John
From 1e4baceaa73dd2d9dd189f71603a7916abd93850 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Thu, 9 Mar 2017 17:09:13 +0800
Subject: [PATCH 3/3] Add a ruler tool to pcbnew GAL

This allows to measure between features on a PCB. It uses a preview
EDA_ITEM in common, but due to the use of the IDs, it's currently
Pcbnew/Modedit only.

This also adds several "utils" for graphical functons useful when
drawing preview items on GAL canvases.

Fixes: lp:1467313
* https://bugs.launchpad.net/kicad/+bug/1467313
---
 bitmaps_png/CMakeLists.txt             |   1 +
 bitmaps_png/cpp_26/measurement.cpp     |  24 +++
 bitmaps_png/sources/measurement.svg    | 140 +++++++++++++++++
 common/CMakeLists.txt                  |   2 +
 common/preview_items/preview_utils.cpp | 175 ++++++++++++++++++++++
 common/preview_items/ruler_item.cpp    | 264 +++++++++++++++++++++++++++++++++
 include/bitmaps.h                      |   1 +
 include/preview_items/preview_utils.h  |  90 +++++++++++
 include/preview_items/ruler_item.h     |  95 ++++++++++++
 pcbnew/edit.cpp                        |   5 +
 pcbnew/modedit.cpp                     |   5 +
 pcbnew/modedit_onclick.cpp             |   5 +
 pcbnew/moduleframe.cpp                 |   4 +-
 pcbnew/onleftclick.cpp                 |   5 +
 pcbnew/pcbframe.cpp                    |   4 +-
 pcbnew/pcbnew_id.h                     |   3 +
 pcbnew/tool_modedit.cpp                |   5 +
 pcbnew/tool_pcb.cpp                    |   5 +
 pcbnew/tools/edit_tool.cpp             |  98 ++++++++++++
 pcbnew/tools/edit_tool.h               |   3 +
 pcbnew/tools/pcb_actions.cpp           |   4 +
 pcbnew/tools/pcb_actions.h             |   1 +
 22 files changed, 935 insertions(+), 4 deletions(-)
 create mode 100644 bitmaps_png/cpp_26/measurement.cpp
 create mode 100644 bitmaps_png/sources/measurement.svg
 create mode 100644 common/preview_items/preview_utils.cpp
 create mode 100644 common/preview_items/ruler_item.cpp
 create mode 100644 include/preview_items/preview_utils.h
 create mode 100644 include/preview_items/ruler_item.h

diff --git a/bitmaps_png/CMakeLists.txt b/bitmaps_png/CMakeLists.txt
index 8dae66fcf..80bdcea45 100644
--- a/bitmaps_png/CMakeLists.txt
+++ b/bitmaps_png/CMakeLists.txt
@@ -339,6 +339,7 @@ set( BMAPS_MID
     load_module_lib
     local_ratsnest
     locked
+    measurement
     mirepcb
     mirror_h
     mirror_v
diff --git a/bitmaps_png/cpp_26/measurement.cpp b/bitmaps_png/cpp_26/measurement.cpp
new file mode 100644
index 000000000..8031f1fbf
--- /dev/null
+++ b/bitmaps_png/cpp_26/measurement.cpp
@@ -0,0 +1,24 @@
+
+/* Do not modify this file, it was automatically generated by the
+ * PNG2cpp CMake script, using a *.png file as input.
+ */
+
+#include <bitmaps.h>
+
+static const unsigned char png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1a, 0x08, 0x04, 0x00, 0x00, 0x00, 0x03, 0x43, 0x84,
+ 0x45, 0x00, 0x00, 0x00, 0x76, 0x49, 0x44, 0x41, 0x54, 0x38, 0xcb, 0x63, 0x60, 0x18, 0x2a, 0x20,
+ 0x35, 0x01, 0x3b, 0x1b, 0x2f, 0x48, 0xfb, 0x92, 0xaa, 0x0d, 0xd5, 0xa2, 0x9d, 0xf6, 0x85, 0x58,
+ 0x4d, 0xff, 0xd3, 0xae, 0xc6, 0x72, 0x33, 0x30, 0xc4, 0x72, 0xa7, 0x5d, 0x4d, 0xfb, 0x4f, 0xbc,
+ 0xa6, 0xff, 0xe9, 0xf3, 0x19, 0x18, 0xd2, 0xe7, 0x83, 0x58, 0x24, 0x68, 0x02, 0x29, 0x86, 0xd1,
+ 0x78, 0x81, 0xdd, 0x25, 0xe3, 0xff, 0xa4, 0x41, 0xa0, 0x26, 0xe3, 0xff, 0x17, 0x49, 0x84, 0x50,
+ 0x4d, 0xc8, 0x1a, 0x09, 0xb3, 0x51, 0x34, 0x11, 0x4b, 0x92, 0xab, 0xc9, 0xf6, 0x1a, 0x19, 0x01,
+ 0x81, 0x3d, 0x90, 0x09, 0x06, 0xfc, 0x70, 0xd4, 0x94, 0x5e, 0x8e, 0x43, 0x53, 0x3d, 0xde, 0xf4,
+ 0x07, 0xd2, 0x86, 0xa1, 0xa9, 0x9e, 0x61, 0x14, 0xe0, 0x07, 0x00, 0x55, 0x88, 0x33, 0x29, 0x9a,
+ 0xe8, 0x0b, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+};
+
+const BITMAP_OPAQUE measurement_xpm[1] = {{ png, sizeof( png ), "measurement_xpm" }};
+
+//EOF
diff --git a/bitmaps_png/sources/measurement.svg b/bitmaps_png/sources/measurement.svg
new file mode 100644
index 000000000..f821f1037
--- /dev/null
+++ b/bitmaps_png/sources/measurement.svg
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   height="26"
+   width="26"
+   version="1.1"
+   id="svg2"
+   inkscape:version="0.92.1 r"
+   sodipodi:docname="measurement.svg">
+  <metadata
+     id="metadata50">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1146"
+     inkscape:window-height="964"
+     id="namedview48"
+     showgrid="false"
+     inkscape:zoom="1"
+     inkscape:cx="18.568355"
+     inkscape:cy="20.983557"
+     inkscape:window-x="768"
+     inkscape:window-y="96"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2"
+     inkscape:snap-to-guides="false"
+     inkscape:snap-grids="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:measure-start="5,19"
+     inkscape:measure-end="14,12">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3006"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4" />
+  <path
+     style="fill:#666666;fill-rule:evenodd"
+     d="m 2,10 v 9 l 2,2 H 5 V 10 H 4"
+     id="path9597"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="cccccc" />
+  <path
+     sodipodi:nodetypes="cccccc"
+     inkscape:connector-curvature="0"
+     id="path9599"
+     d="m 15,10 v 9 l -2,2 H 12 V 10 h 1"
+     style="fill:#666666;fill-rule:evenodd" />
+  <path
+     style="fill:#666666;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="M 8,9 H 5 V 3 l 3,4 z"
+     id="path9621"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path9623"
+     d="m 9,9 h 3 V 3 L 9,7 Z"
+     style="fill:#666666;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     style="opacity:1;fill:#d1d1d1;fill-opacity:1;stroke:none;stroke-width:1"
+     d="M 1.4999999,8.5 H 26 v 5 H 1.4999999 Z"
+     id="rect9595"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 21.5,10 v 3"
+     id="path9601"
+     inkscape:connector-curvature="0" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path9603"
+     d="m 18.5,11 v 2"
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path9605"
+     d="m 15.5,10 v 3"
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="m 12.5,11 v 2"
+     id="path9607"
+     inkscape:connector-curvature="0" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path9609"
+     d="m 9.5,10 v 3"
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="m 6.5,11 v 2"
+     id="path9611"
+     inkscape:connector-curvature="0" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 3.5,10 v 3"
+     id="path9613"
+     inkscape:connector-curvature="0" />
+  <path
+     style="opacity:1;fill:none;fill-opacity:1;stroke:#333333;stroke-width:1"
+     d="M 26,13.5 H 1.7613635 c -0.1447954,0 -0.2613636,-0.1115 -0.2613636,-0.25 v -4.5 c 0,-0.1385 0.1165682,-0.25 0.2613636,-0.25 H 26"
+     id="rect9617"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="cssssc" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="m 24.5,11 v 2"
+     id="path9625"
+     inkscape:connector-curvature="0" />
+</svg>
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index a89cc6b1a..d5df20ec6 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -184,6 +184,8 @@ set( COMMON_PAGE_LAYOUT_SRCS
     )
 
 set( COMMON_PREVIEW_ITEMS_SRCS
+    preview_items/preview_utils.cpp
+    preview_items/ruler_item.cpp
     preview_items/simple_overlay_item.cpp
     preview_items/selection_area.cpp
     )
diff --git a/common/preview_items/preview_utils.cpp b/common/preview_items/preview_utils.cpp
new file mode 100644
index 000000000..bdd223794
--- /dev/null
+++ b/common/preview_items/preview_utils.cpp
@@ -0,0 +1,175 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 Kicad Developers, see change_log.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 <preview_items/preview_utils.h>
+
+#include <gal/graphics_abstraction_layer.h>
+
+#include <base_units.h>
+
+using namespace KIGFX;
+
+
+COLOR4D KIGFX::PREVIEW::PreviewOverlayDefaultColor()
+{
+    return COLOR4D( 1.0, 1.0, 1.0, 1.0 );
+}
+
+
+double KIGFX::PREVIEW::PreviewOverlayFillAlpha()
+{
+    return 0.2;
+}
+
+
+double KIGFX::PREVIEW::PreviewOverlayDeemphAlpha( bool aDeemph )
+{
+    return aDeemph ? 0.5 : 1.0;
+}
+
+
+COLOR4D KIGFX::PREVIEW::PreviewOverlaySpecialAngleColor()
+{
+    return COLOR4D( 0.5, 1.0, 0.5, 1.0 );
+}
+
+
+static wxString getDimensionUnit( EDA_UNITS_T aUnits )
+{
+    switch( aUnits )
+    {
+    case INCHES:
+        return _( "\"" );
+
+    case MILLIMETRES:
+        return _( "mm" );
+
+    case DEGREES:
+        return _( "°" );
+
+    case UNSCALED_UNITS:
+        break;
+    // no default: handle all cases
+    }
+
+    return wxEmptyString;
+}
+
+static wxString formatPreviewDimension( double aVal, EDA_UNITS_T aUnits )
+{
+    int precision = 4;
+
+    // show a sane precision for the preview, which doesn't need to
+    // be accurate down to the nanometre
+    switch( aUnits )
+    {
+    case MILLIMETRES:
+        precision = 2;  // 10um
+        break;
+    case INCHES:
+        precision = 4;  // 1mil
+        break;
+    case DEGREES:
+        precision = 1;  // 0.1deg (limit of formats anyway)
+        break;
+    case UNSCALED_UNITS:
+        break;
+    }
+
+    const wxString fmtStr = wxString::Format( "%%.%df", precision );
+
+    wxString str = wxString::Format( fmtStr, To_User_Unit( aUnits, aVal ) );
+
+    const wxString symbol = getDimensionUnit( aUnits );
+
+    if( symbol.size() )
+        str << " " << symbol;
+
+    return str;
+}
+
+wxString KIGFX::PREVIEW::DimensionLabel( const wxString& prefix,
+            double aVal, EDA_UNITS_T aUnits )
+{
+    wxString str;
+
+    if( prefix.size() )
+        str << prefix << ": ";
+
+    str << formatPreviewDimension( aVal, aUnits );
+    return str;
+}
+
+
+void KIGFX::PREVIEW::SetConstantGlyphHeight( KIGFX::GAL& aGal, double aHeight )
+{
+    aHeight /= aGal.GetWorldScale();
+
+    auto glyphSize = aGal.GetGlyphSize();
+    glyphSize = glyphSize * ( aHeight / glyphSize.y );
+    aGal.SetGlyphSize( glyphSize );
+}
+
+
+void KIGFX::PREVIEW::DrawTextNextToCursor( KIGFX::GAL& aGal,
+        const VECTOR2D& aCursorPos, const VECTOR2D& aTextQuadrant,
+        std::vector<wxString> aStrings )
+{
+    auto glyphSize = aGal.GetGlyphSize();
+
+    const auto lineSpace = glyphSize.y * 0.2;
+    auto linePitch = glyphSize.y + lineSpace;
+
+    // radius string goes on the right of the cursor centre line
+    // with a small horizontal offset (enough to keep clear of a
+    // system cursor if present)
+    auto textPos = aCursorPos;
+
+    // if the text goes above the cursor, shift it up
+    if( aTextQuadrant.y > 0 )
+    {
+        textPos.y -= linePitch * ( aStrings.size() + 1 );
+    }
+
+    if( aTextQuadrant.x < 0 )
+    {
+        aGal.SetHorizontalJustify( GR_TEXT_HJUSTIFY_LEFT );
+        textPos.x += 15.0 / aGal.GetWorldScale();
+    }
+    else
+    {
+        aGal.SetHorizontalJustify( GR_TEXT_HJUSTIFY_RIGHT );
+        textPos.x -= 15.0 / aGal.GetWorldScale();
+    }
+
+    aGal.SetStrokeColor( PreviewOverlayDefaultColor().WithAlpha(
+                            PreviewOverlayDeemphAlpha( true ) ) );
+    aGal.SetIsFill( false );
+
+    // write strings top-to-bottom
+    for( auto& str : aStrings )
+    {
+        textPos.y += linePitch;
+        aGal.BitmapText( str, textPos, 0.0 );
+    }
+}
diff --git a/common/preview_items/ruler_item.cpp b/common/preview_items/ruler_item.cpp
new file mode 100644
index 000000000..15d77ad4f
--- /dev/null
+++ b/common/preview_items/ruler_item.cpp
@@ -0,0 +1,264 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@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 <preview_items/ruler_item.h>
+
+#include <preview_items/preview_utils.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <layers_id_colors_and_visibility.h>
+#include <view/view.h>
+
+#include <base_units.h>
+#include <common.h>
+
+using namespace KIGFX::PREVIEW;
+
+static const double maxTickDensity = 10.0;       // min pixels between tick marks
+static const double midTickLengthFactor = 1.5;
+static const double majorTickLengthFactor = 2.5;
+
+
+static void drawCursorStrings( KIGFX::GAL& aGal, const VECTOR2D& aCursor,
+    const VECTOR2D& aRulerVec )
+{
+    // draw the cursor labels
+    std::vector<wxString> cursorStrings;
+
+    cursorStrings.push_back( DimensionLabel( "r", aRulerVec.EuclideanNorm(), g_UserUnit ) );
+
+    double degs = RAD2DECIDEG( -aRulerVec.Angle() );
+    cursorStrings.push_back( DimensionLabel("θ", degs, DEGREES ) );
+
+    for( auto& str: cursorStrings )
+    {
+        // FIXME: remove spaces that choke OpenGL lp:1668455
+        str.erase( std::remove( str.begin(), str.end(), ' ' ),
+                   str.end() );
+    }
+
+    auto temp = aRulerVec;
+    DrawTextNextToCursor( aGal, aCursor, -temp, cursorStrings );
+}
+
+
+/**
+ * Description of a "tick format" for a scale factor - how many ticks there are
+ * between medium/major ticks and how each scale relates to the last one
+ */
+struct TICK_FORMAT
+{
+    double divisionBase;    ///> multiple from the last scale
+    int majorStep;          ///> ticks between major ticks
+    int midStep;            ///> ticks between medium ticks (0 if no medium ticks)
+};
+
+
+static TICK_FORMAT getTickFormatForScale( double aScale, double& aTickSpace )
+{
+    // simple 1/2/5 scales per decade
+    static std::vector<TICK_FORMAT> tickFormats =
+    {
+        { 2,    10,     5 },    // |....:....|
+        { 2,     5,     0 },    // |....|
+        { 2.5,   2,     0 },    // |.|.|
+    };
+
+    // could start at a set number of MM, but that's not available in common
+    aTickSpace = 1;
+
+    // convert to a round (mod-10) number of mils
+    if( g_UserUnit == INCHES )
+    {
+        aTickSpace *= 2.54;
+    }
+
+    int tickFormat = 0;
+
+    while( true )
+    {
+        const auto pixelSpace = aTickSpace * aScale;
+
+        if( pixelSpace >= maxTickDensity)
+            break;
+
+        tickFormat = (tickFormat + 1) % tickFormats.size();
+        aTickSpace *= tickFormats[tickFormat].divisionBase;
+    }
+
+    return tickFormats[tickFormat];
+}
+
+
+/**
+ * Draw labelled ticks on a line. Ticks are spaced according to a
+ * maximum density. Miror ticks are not labelled.
+ *
+ * @param aGal the GAL to draw on
+ * @param aOrigin start of line to draw ticks on
+ * @param aLine line vector
+ * @param aMinorTickLen length of minor ticks in IU
+ */
+void drawTicksAlongLine( KIGFX::GAL& aGal, const VECTOR2D& aOrigin,
+        const VECTOR2D& aLine, double aMinorTickLen )
+{
+    VECTOR2D tickLine = aLine.Rotate( -M_PI_2 );
+
+    double tickSpace;
+    TICK_FORMAT tickF = getTickFormatForScale( aGal.GetWorldScale(), tickSpace );
+
+    // number of ticks in whole ruler
+    int numTicks = (int) std::ceil( aLine.EuclideanNorm() / tickSpace );
+
+    // work out which way up the tick labels go
+    double labelAngle = -tickLine.Angle();
+
+    if( aLine.Angle() > 0 )
+    {
+        aGal.SetHorizontalJustify( GR_TEXT_HJUSTIFY_LEFT );
+    }
+    else
+    {
+        aGal.SetHorizontalJustify( GR_TEXT_HJUSTIFY_RIGHT );
+        labelAngle += M_PI;
+    }
+
+    // text and ticks are dimmed
+    aGal.SetStrokeColor( PreviewOverlayDefaultColor().WithAlpha( PreviewOverlayDeemphAlpha( true ) ) );
+
+    const auto labelOffset = tickLine.Resize( aMinorTickLen * (majorTickLengthFactor + 1) );
+
+    for( int i = 0; i < numTicks; ++i )
+    {
+        const auto tickPos = aOrigin + aLine.Resize( tickSpace * i );
+
+        double length = aMinorTickLen;
+        bool drawLabel = false;
+
+        if( i % tickF.majorStep == 0)
+        {
+            drawLabel = true;
+            length *= majorTickLengthFactor;
+        }
+        else if ( tickF.midStep && i % tickF.midStep == 0 )
+        {
+            drawLabel = true;
+            length *= midTickLengthFactor;
+        }
+
+        aGal.DrawLine( tickPos, tickPos + tickLine.Resize( length ) );
+
+        if( drawLabel )
+        {
+            wxString label = DimensionLabel( "", tickSpace * i, g_UserUnit );
+
+            // FIXME: spaces choke OpenGL lp:1668455
+            label.erase( std::remove( label.begin(), label.end(), ' ' ),
+                      label.end() );
+
+            aGal.BitmapText( label, tickPos + labelOffset, labelAngle );
+        }
+    }
+}
+
+
+/**
+ * Draw simple ticks on the back of a line such that the line is
+ * divided into n parts.
+ *
+ * @param aGal the GAL to draw on
+ * @param aOrigin start of line to draw ticks on
+ * @param aLine line vector
+ * @param aTickLen length of ticks in IU
+ * @param aNumDivisions number of parts to divide the line into
+ */
+void drawBacksideTicks( KIGFX::GAL& aGal, const VECTOR2D& aOrigin,
+        const VECTOR2D& aLine, double aTickLen, int aNumDivisions )
+{
+    const double backTickSpace = aLine.EuclideanNorm() / aNumDivisions;
+    const auto backTickVec = aLine.Rotate( M_PI_2 ).Resize( aTickLen );
+
+    for( int i = 0; i < aNumDivisions + 1; ++i )
+    {
+        const auto backTickPos = aOrigin + aLine.Resize( backTickSpace * i );
+        aGal.DrawLine( backTickPos, backTickPos + backTickVec );
+    }
+}
+
+
+RULER_ITEM::RULER_ITEM():
+    EDA_ITEM( NOT_USED )    // Never added to anything - just a preview
+{}
+
+
+const BOX2I RULER_ITEM::ViewBBox() const
+{
+    BOX2I tmp;
+
+    tmp.SetOrigin( m_origin );
+    tmp.SetEnd( m_end );
+    tmp.Normalize();
+    return tmp;
+}
+
+
+void RULER_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
+{
+    aLayers[0] = ITEM_GAL_LAYER( GP_OVERLAY );
+    aCount = 1;
+}
+
+
+void RULER_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
+{
+    auto& gal = *aView->GetGAL();
+
+    gal.SetLineWidth( 1.0 );
+    gal.SetIsStroke( true );
+    gal.SetIsFill( false );
+    gal.SetStrokeColor( PreviewOverlayDefaultColor() );
+
+    // draw the main line from the origin to cursor
+    gal.DrawLine( m_origin, m_end );
+
+    VECTOR2D rulerVec( m_end - m_origin );
+
+    // constant text size on screen
+    SetConstantGlyphHeight( gal, 12.0 );
+
+    drawCursorStrings( gal, m_end, rulerVec );
+
+    // tick label size
+    SetConstantGlyphHeight( gal, 10.0 );
+
+    // basic tick size
+    const double minorTickLen = 5.0 / gal.GetWorldScale();
+
+    drawTicksAlongLine( gal, m_origin, rulerVec, minorTickLen );
+
+    gal.SetStrokeColor( PreviewOverlayDefaultColor().WithAlpha( PreviewOverlayDeemphAlpha( true ) ) );
+    drawBacksideTicks( gal, m_origin, rulerVec, minorTickLen * majorTickLengthFactor, 2 );
+
+    // draw the back of the origin "crosshair"
+    gal.DrawLine( m_origin, m_origin + rulerVec.Resize( -minorTickLen * midTickLengthFactor ) );
+}
diff --git a/include/bitmaps.h b/include/bitmaps.h
index 8f63a8449..e58ade38d 100644
--- a/include/bitmaps.h
+++ b/include/bitmaps.h
@@ -283,6 +283,7 @@ EXTERN_BITMAP( load_module_board_xpm )
 EXTERN_BITMAP( load_module_lib_xpm )
 EXTERN_BITMAP( local_ratsnest_xpm )
 EXTERN_BITMAP( locked_xpm )
+EXTERN_BITMAP( measurement_xpm )
 EXTERN_BITMAP( mirepcb_xpm )
 EXTERN_BITMAP( mirror_h_xpm )
 EXTERN_BITMAP( mirror_v_xpm )
diff --git a/include/preview_items/preview_utils.h b/include/preview_items/preview_utils.h
new file mode 100644
index 000000000..31a08fd5d
--- /dev/null
+++ b/include/preview_items/preview_utils.h
@@ -0,0 +1,90 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 Kicad Developers, see change_log.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
+ */
+
+#ifndef PREVIEW_PREVIEW_UTILS__H_
+#define PREVIEW_PREVIEW_UTILS__H_
+
+#include <common.h>
+#include <gal/color4d.h>
+#include <math/vector2d.h>
+
+namespace KIGFX
+{
+class GAL;
+
+namespace PREVIEW
+{
+
+/**
+ * The default fill/stroke color of preview overlay items
+ */
+COLOR4D PreviewOverlayDefaultColor();
+
+/**
+ * The default alpha of overlay fills
+ */
+double PreviewOverlayFillAlpha();
+
+/**
+ * Default alpha of "de-emphasised" features (like previously locked-in
+ * lines
+ */
+double PreviewOverlayDeemphAlpha( bool aDeemph = true );
+
+/**
+ * The colour of "special" angle overlay features
+ */
+COLOR4D PreviewOverlaySpecialAngleColor();
+
+/**
+ * Get a formatted string showing a dimension to a sane precision
+ * with an optional prefix and unit suffix.
+ */
+wxString DimensionLabel( const wxString& prefix, double aVal,
+                         EDA_UNITS_T aUnits );
+
+/**
+ * Set the GAL glyph height to a constant scaled value, so that it
+ * always looks the same on screen
+ *
+ * @param aHeight the height of the glyph, in pixels
+ */
+void SetConstantGlyphHeight( KIGFX::GAL& aGal, double aHeight );
+
+/**
+ * Draw strings next to the cursor
+ *
+ * @param aGal the GAL to draw on
+ * @param aCursorPos the position of the cursor to draw next to
+ * @param aTextQuadrant a vector pointing to the quadrant to draw the
+ * text in
+ * @param aStrings list of strings to draw, top to bottom
+ */
+void DrawTextNextToCursor( KIGFX::GAL& aGal,
+        const VECTOR2D& aCursorPos, const VECTOR2D& aTextQuadrant,
+        std::vector<wxString> aStrings );
+
+} // PREVIEW
+} // KIGFX
+
+#endif  // PREVIEW_PREVIEW_UTILS__H_
diff --git a/include/preview_items/ruler_item.h b/include/preview_items/ruler_item.h
new file mode 100644
index 000000000..ea32d83c6
--- /dev/null
+++ b/include/preview_items/ruler_item.h
@@ -0,0 +1,95 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 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
+ */
+
+#ifndef PREVIEW_ITEMS_RULER_ITEM_H
+#define PREVIEW_ITEMS_RULER_ITEM_H
+
+#include <base_struct.h>
+
+namespace KIGFX
+{
+class GAL;
+
+namespace PREVIEW
+{
+
+/**
+ * Class RULER_ITEM
+ *
+ * A drawn ruler item for showing the distance between two points.
+ */
+class RULER_ITEM : public EDA_ITEM
+{
+public:
+
+    RULER_ITEM();
+
+    ///> @copydoc EDA_ITEM::ViewBBox()
+    const BOX2I ViewBBox() const override;
+
+    ///> @copydoc EDA_ITEM::ViewGetLayers()
+    void ViewGetLayers( int aLayers[], int& aCount ) const override;
+
+    ///> @copydoc EDA_ITEM::ViewDraw();
+    void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
+
+
+#if defined(DEBUG)
+    void Show( int x, std::ostream& st ) const override
+    {
+    }
+#endif
+
+    /**
+     * Get class name
+     * @return  string "RULER_ITEM"
+     */
+    wxString GetClass() const override
+    {
+        return wxT( "RULER_ITEM" );
+    }
+
+    ///> Set the origin of the ruler (the fixed end)
+    void SetOrigin( VECTOR2I aOrigin )
+    {
+        m_origin = aOrigin;
+    }
+
+    /**
+     * Set the current end of the rectangle (the end that moves
+     * with the cursor.
+     */
+    void SetEnd( VECTOR2I aEnd )
+    {
+        m_end = aEnd;
+    }
+
+private:
+
+    VECTOR2I m_origin, m_end;
+};
+
+} // PREVIEW
+} // KIGFX
+
+#endif // PREVIEW_ITEMS_RULER_ITEM_H
diff --git a/pcbnew/edit.cpp b/pcbnew/edit.cpp
index 8d1fa6dc4..22307a886 100644
--- a/pcbnew/edit.cpp
+++ b/pcbnew/edit.cpp
@@ -1525,6 +1525,11 @@ void PCB_EDIT_FRAME::OnSelectTool( wxCommandEvent& aEvent )
             Compile_Ratsnest( &dc, true );
 
         break;
+
+    // collect GAL-only tools here
+    case ID_PCB_MEASUREMENT_TOOL:
+        SetToolID( id, wxCURSOR_DEFAULT, _( "Unsupported tool in this canvas" ) );
+        break;
     }
 }
 
diff --git a/pcbnew/modedit.cpp b/pcbnew/modedit.cpp
index 2a69f0ddc..ddf96811e 100644
--- a/pcbnew/modedit.cpp
+++ b/pcbnew/modedit.cpp
@@ -962,6 +962,11 @@ void FOOTPRINT_EDIT_FRAME::OnVerticalToolbar( wxCommandEvent& aEvent )
         SetToolID( id, wxCURSOR_BULLSEYE, _( "Delete item" ) );
         break;
 
+    case ID_MODEDIT_MEASUREMENT_TOOL:
+        DisplayError( this, wxT( "Unsupported tool in legacy canvas" ) );
+        SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
+        break;
+
     default:
         wxFAIL_MSG( wxT( "Unknown command id." ) );
         SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
diff --git a/pcbnew/modedit_onclick.cpp b/pcbnew/modedit_onclick.cpp
index 60ad2bc81..4bf4c4ae7 100644
--- a/pcbnew/modedit_onclick.cpp
+++ b/pcbnew/modedit_onclick.cpp
@@ -200,6 +200,11 @@ void FOOTPRINT_EDIT_FRAME::OnLeftClick( wxDC* DC, const wxPoint& MousePos )
 
         break;
 
+    case ID_MODEDIT_MEASUREMENT_TOOL:
+        DisplayError( this, wxT( "Unsupported tool in legacy canvas" ) );
+        SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
+        break;
+
     default:
         DisplayError( this, wxT( "FOOTPRINT_EDIT_FRAME::ProcessCommand error" ) );
         SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
diff --git a/pcbnew/moduleframe.cpp b/pcbnew/moduleframe.cpp
index 337c3f083..2255d1f59 100644
--- a/pcbnew/moduleframe.cpp
+++ b/pcbnew/moduleframe.cpp
@@ -116,7 +116,7 @@ BEGIN_EVENT_TABLE( FOOTPRINT_EDIT_FRAME, PCB_BASE_FRAME )
     // Vertical tool bar button click event handler.
     EVT_TOOL( ID_NO_TOOL_SELECTED, FOOTPRINT_EDIT_FRAME::OnVerticalToolbar )
     EVT_TOOL( ID_ZOOM_SELECTION, FOOTPRINT_EDIT_FRAME::OnVerticalToolbar )
-    EVT_TOOL_RANGE( ID_MODEDIT_PAD_TOOL, ID_MODEDIT_PLACE_GRID_COORD,
+    EVT_TOOL_RANGE( ID_MODEDIT_PAD_TOOL, ID_MODEDIT_MEASUREMENT_TOOL,
                     FOOTPRINT_EDIT_FRAME::OnVerticalToolbar )
 
     // Options Toolbar (ID_TB_OPTIONS_SHOW_PADS_SKETCH id is managed in PCB_BASE_FRAME)
@@ -195,7 +195,7 @@ BEGIN_EVENT_TABLE( FOOTPRINT_EDIT_FRAME, PCB_BASE_FRAME )
     EVT_UPDATE_UI( ID_NO_TOOL_SELECTED, FOOTPRINT_EDIT_FRAME::OnUpdateVerticalToolbar )
     EVT_UPDATE_UI( ID_ZOOM_SELECTION, FOOTPRINT_EDIT_FRAME::OnUpdateVerticalToolbar )
 
-    EVT_UPDATE_UI_RANGE( ID_MODEDIT_PAD_TOOL, ID_MODEDIT_PLACE_GRID_COORD,
+    EVT_UPDATE_UI_RANGE( ID_MODEDIT_PAD_TOOL, ID_MODEDIT_MEASUREMENT_TOOL,
                          FOOTPRINT_EDIT_FRAME::OnUpdateVerticalToolbar )
 
     // Option toolbar:
diff --git a/pcbnew/onleftclick.cpp b/pcbnew/onleftclick.cpp
index a43a43ab5..38b9ef08f 100644
--- a/pcbnew/onleftclick.cpp
+++ b/pcbnew/onleftclick.cpp
@@ -439,6 +439,11 @@ void PCB_EDIT_FRAME::OnLeftClick( wxDC* aDC, const wxPoint& aPosition )
         m_canvas->DrawGridAxis( aDC, GR_COPY, GetBoard()->GetGridOrigin() );
         break;
 
+    case ID_PCB_MEASUREMENT_TOOL:
+        DisplayError( this, wxT( "This tool is not available in the legacy canvas" ) );
+        SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
+        break;
+
     default:
         DisplayError( this, wxT( "PCB_EDIT_FRAME::OnLeftClick() id error" ) );
         SetToolID( ID_NO_TOOL_SELECTED, m_canvas->GetDefaultCursor(), wxEmptyString );
diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp
index bfbe39d4a..4927fdc16 100644
--- a/pcbnew/pcbframe.cpp
+++ b/pcbnew/pcbframe.cpp
@@ -259,7 +259,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
     // Vertical main toolbar:
     EVT_TOOL( ID_NO_TOOL_SELECTED, PCB_EDIT_FRAME::OnSelectTool )
     EVT_TOOL( ID_ZOOM_SELECTION, PCB_EDIT_FRAME::OnSelectTool )
-    EVT_TOOL_RANGE( ID_PCB_HIGHLIGHT_BUTT, ID_PCB_PLACE_GRID_COORD_BUTT,
+    EVT_TOOL_RANGE( ID_PCB_HIGHLIGHT_BUTT, ID_PCB_MEASUREMENT_TOOL,
                     PCB_EDIT_FRAME::OnSelectTool )
 
     EVT_TOOL_RANGE( ID_PCB_MUWAVE_START_CMD, ID_PCB_MUWAVE_END_CMD,
@@ -311,7 +311,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
                          PCB_EDIT_FRAME::OnUpdateSelectTrackWidth )
     EVT_UPDATE_UI_RANGE( ID_POPUP_PCB_SELECT_VIASIZE1, ID_POPUP_PCB_SELECT_VIASIZE8,
                          PCB_EDIT_FRAME::OnUpdateSelectViaSize )
-    EVT_UPDATE_UI_RANGE( ID_PCB_HIGHLIGHT_BUTT, ID_PCB_PLACE_GRID_COORD_BUTT,
+    EVT_UPDATE_UI_RANGE( ID_PCB_HIGHLIGHT_BUTT, ID_PCB_MEASUREMENT_TOOL,
                          PCB_EDIT_FRAME::OnUpdateVerticalToolbar )
     EVT_UPDATE_UI_RANGE( ID_TB_OPTIONS_SHOW_ZONES, ID_TB_OPTIONS_SHOW_ZONES_OUTLINES_ONLY,
                          PCB_EDIT_FRAME::OnUpdateZoneDisplayStyle )
diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h
index eced5cb96..e092e9c8f 100644
--- a/pcbnew/pcbnew_id.h
+++ b/pcbnew/pcbnew_id.h
@@ -40,6 +40,8 @@ enum pcbnew_ids
     ID_PCB_DELETE_ITEM_BUTT,
     ID_PCB_PLACE_OFFSET_COORD_BUTT,
     ID_PCB_PLACE_GRID_COORD_BUTT,
+    ID_PCB_MEASUREMENT_TOOL,
+
     ID_DIFF_PAIR_BUTT,
     ID_TUNE_SINGLE_TRACK_LEN_BUTT,
     ID_TUNE_DIFF_PAIR_LEN_BUTT,
@@ -343,6 +345,7 @@ enum pcbnew_ids
     ID_MODEDIT_ANCHOR_TOOL,
     ID_MODEDIT_DELETE_TOOL,
     ID_MODEDIT_PLACE_GRID_COORD,
+    ID_MODEDIT_MEASUREMENT_TOOL,
 
     // ID used in module editor:
     ID_POPUP_MODEDIT_GLOBAL_EDIT_EDGE,
diff --git a/pcbnew/tool_modedit.cpp b/pcbnew/tool_modedit.cpp
index a7953ae9f..42dd2411a 100644
--- a/pcbnew/tool_modedit.cpp
+++ b/pcbnew/tool_modedit.cpp
@@ -196,6 +196,11 @@ void FOOTPRINT_EDIT_FRAME::ReCreateVToolbar()
                             _( "Set the origin point for the grid" ),
                             wxITEM_CHECK );
 
+    m_drawToolBar->AddTool( ID_MODEDIT_MEASUREMENT_TOOL, wxEmptyString,
+                            KiBitmap( measurement_xpm ),
+                            _( "Measure between two points" ),
+                            wxITEM_CHECK );
+
     m_drawToolBar->Realize();
 }
 
diff --git a/pcbnew/tool_pcb.cpp b/pcbnew/tool_pcb.cpp
index 0750e9ffc..28fdedb7d 100644
--- a/pcbnew/tool_pcb.cpp
+++ b/pcbnew/tool_pcb.cpp
@@ -484,6 +484,11 @@ void PCB_EDIT_FRAME::ReCreateVToolbar()
                             _( "Set the origin point for the grid" ),
                             wxITEM_CHECK );
 
+    m_drawToolBar->AddTool( ID_PCB_MEASUREMENT_TOOL, wxEmptyString,
+                            KiBitmap( measurement_xpm ),
+                            _( "Activate the measurement tool" ),
+                            wxITEM_CHECK );
+
     m_drawToolBar->Realize();
 }
 
diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp
index b26aa4b55..9f64b4d60 100644
--- a/pcbnew/tools/edit_tool.cpp
+++ b/pcbnew/tools/edit_tool.cpp
@@ -35,6 +35,7 @@
 #include <class_draw_panel_gal.h>
 #include <module_editor_frame.h>
 #include <array_creator.h>
+#include <pcbnew_id.h>
 
 #include <tool/tool_manager.h>
 #include <view/view_controls.h>
@@ -62,6 +63,8 @@ using namespace std::placeholders;
 
 #include <tools/tool_event_utils.h>
 
+#include <preview_items/ruler_item.h>
+
 #include <board_commit.h>
 
 // Edit tool actions
@@ -149,6 +152,11 @@ TOOL_ACTION PCB_ACTIONS::editModifiedSelection( "pcbnew.InteractiveEdit.Modified
         AS_GLOBAL, 0,
         "", "" );
 
+TOOL_ACTION PCB_ACTIONS::measureTool( "pcbnew.InteractiveEdit.measureTool",
+        AS_GLOBAL, MD_CTRL + MD_SHIFT + 'M',
+        _( "Measure tool" ), _( "Interactively measure between points" ),
+        nullptr, AF_ACTIVATE );
+
 
 EDIT_TOOL::EDIT_TOOL() :
     PCB_TOOL( "pcbnew.InteractiveEdit" ), m_selectionTool( NULL ),
@@ -927,6 +935,95 @@ int EDIT_TOOL::ExchangeFootprints( const TOOL_EVENT& aEvent )
 }
 
 
+int EDIT_TOOL::MeasureTool( const TOOL_EVENT& aEvent )
+{
+    auto& view = *getView();
+    auto& controls = *getViewControls();
+
+    Activate();
+    frame()->SetToolID( EditingModules() ? ID_MODEDIT_MEASUREMENT_TOOL
+                                         : ID_PCB_MEASUREMENT_TOOL,
+                        wxCURSOR_PENCIL, _( "Measure between two points" ) );
+
+    KIGFX::PREVIEW::RULER_ITEM ruler;
+    view.Add( &ruler );
+    view.SetVisible( &ruler, false );
+
+    bool originSet = false;
+
+    controls.ShowCursor( true );
+    controls.SetSnapping( true );
+
+    while( auto evt = Wait() )
+    {
+        const VECTOR2I cursorPos = controls.GetCursorPosition();
+
+        if( evt->IsCancel() || evt->IsActivate() )
+        {
+            break;
+        }
+
+        // click or drag starts
+        else if( !originSet &&
+                ( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) )
+        {
+            if( !evt->IsDrag( BUT_LEFT ) )
+            {
+                ruler.SetOrigin( cursorPos );
+                ruler.SetEnd( cursorPos );
+            }
+
+            controls.CaptureCursor( true );
+            controls.SetAutoPan( true );
+
+            originSet = true;
+        }
+
+        else if( !originSet && evt->IsMotion() )
+        {
+            // make sure the origin is set before a drag starts
+            // otherwise you can miss a step
+            ruler.SetOrigin( cursorPos );
+            ruler.SetEnd( cursorPos );
+        }
+
+        // second click or mouse up after drag ends
+        else if( originSet &&
+                ( evt->IsClick( BUT_LEFT ) || evt->IsMouseUp( BUT_LEFT ) ) )
+        {
+            originSet = false;
+
+            controls.SetAutoPan( false );
+            controls.CaptureCursor( false );
+
+            view.SetVisible( &ruler, false );
+        }
+
+        // move or drag when origin set updates rules
+        else if( originSet &&
+                ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
+        {
+            ruler.SetEnd( cursorPos );
+
+            view.SetVisible( &ruler, true );
+            view.Update( &ruler, KIGFX::GEOMETRY );
+        }
+
+        else if( evt->IsClick( BUT_RIGHT ) )
+        {
+            GetManager()->PassEvent();
+        }
+    }
+
+    view.SetVisible( &ruler, false );
+    view.Remove( &ruler );
+
+    frame()->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
+
+    return 0;
+}
+
+
 void EDIT_TOOL::SetTransitions()
 {
     Go( &EDIT_TOOL::Main,       PCB_ACTIONS::editActivate.MakeEvent() );
@@ -943,6 +1040,7 @@ void EDIT_TOOL::SetTransitions()
     Go( &EDIT_TOOL::Mirror,     PCB_ACTIONS::mirror.MakeEvent() );
     Go( &EDIT_TOOL::editFootprintInFpEditor, PCB_ACTIONS::editFootprintInFpEditor.MakeEvent() );
     Go( &EDIT_TOOL::ExchangeFootprints,      PCB_ACTIONS::exchangeFootprints.MakeEvent() );
+    Go( &EDIT_TOOL::MeasureTool,             PCB_ACTIONS::measureTool.MakeEvent() );
 }
 
 
diff --git a/pcbnew/tools/edit_tool.h b/pcbnew/tools/edit_tool.h
index 8df612292..afd2f1209 100644
--- a/pcbnew/tools/edit_tool.h
+++ b/pcbnew/tools/edit_tool.h
@@ -124,6 +124,9 @@ public:
      */
     int ExchangeFootprints( const TOOL_EVENT& aEvent );
 
+    ///> Launches a tool to measure between points
+    int MeasureTool( const TOOL_EVENT& aEvent );
+
     ///> Sets up handlers for various events.
     void SetTransitions() override;
 
diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp
index a4fb3301c..486450f9f 100644
--- a/pcbnew/tools/pcb_actions.cpp
+++ b/pcbnew/tools/pcb_actions.cpp
@@ -151,6 +151,10 @@ boost::optional<TOOL_EVENT> PCB_ACTIONS::TranslateLegacyId( int aId )
     case ID_PCB_PLACE_OFFSET_COORD_BUTT:
         return PCB_ACTIONS::drillOrigin.MakeEvent();
 
+    case ID_PCB_MEASUREMENT_TOOL:
+    case ID_MODEDIT_MEASUREMENT_TOOL:
+        return PCB_ACTIONS::measureTool.MakeEvent();
+
     case ID_PCB_HIGHLIGHT_BUTT:
         return PCB_ACTIONS::highlightNetCursor.MakeEvent();
 
diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h
index 25f8fd5e8..21615c7ff 100644
--- a/pcbnew/tools/pcb_actions.h
+++ b/pcbnew/tools/pcb_actions.h
@@ -324,6 +324,7 @@ public:
     static TOOL_ACTION zoomTool;
     static TOOL_ACTION pickerTool;
     static TOOL_ACTION resetCoords;
+    static TOOL_ACTION measureTool;
     static TOOL_ACTION switchCursor;
     static TOOL_ACTION switchUnits;
     static TOOL_ACTION deleteItemCursor;
-- 
2.12.0

From 7326feba75f1a8ffa165398dc362ceed721b781c Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Mon, 27 Feb 2017 02:06:01 +0800
Subject: [PATCH 2/3] Make SELECTION_AREA a generic overlay item

This simplifies the (already simple) SELECTION_AREA class. It is also
moved into KIGFX::PREVIEW and put in the common library, where it can be
reused by other GAL-aware tools (not just in Pcbnew) in future.
---
 common/CMakeLists.txt                              |  1 +
 .../preview_items}/selection_area.cpp              | 37 +++++----------
 .../preview_items}/selection_area.h                | 55 ++++++++++++----------
 pcbnew/CMakeLists.txt                              |  1 -
 pcbnew/tools/selection_tool.cpp                    |  4 +-
 pcbnew/tools/selection_tool.h                      |  1 -
 pcbnew/tools/zoom_tool.cpp                         |  4 +-
 7 files changed, 49 insertions(+), 54 deletions(-)
 rename {pcbnew/tools => common/preview_items}/selection_area.cpp (66%)
 rename {pcbnew/tools => include/preview_items}/selection_area.h (65%)

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index d2910369a..a89cc6b1a 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -185,6 +185,7 @@ set( COMMON_PAGE_LAYOUT_SRCS
 
 set( COMMON_PREVIEW_ITEMS_SRCS
     preview_items/simple_overlay_item.cpp
+    preview_items/selection_area.cpp
     )
 
 set( COMMON_SRCS
diff --git a/pcbnew/tools/selection_area.cpp b/common/preview_items/selection_area.cpp
similarity index 66%
rename from pcbnew/tools/selection_area.cpp
rename to common/preview_items/selection_area.cpp
index 7e4e900a8..f66cb1dee 100644
--- a/pcbnew/tools/selection_area.cpp
+++ b/common/preview_items/selection_area.cpp
@@ -22,13 +22,20 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-#include "selection_area.h"
-#include <gal/graphics_abstraction_layer.h>
-#include <gal/color4d.h>
+#include <preview_items/selection_area.h>
 
+#include <gal/graphics_abstraction_layer.h>
 #include <view/view.h>
 
-using namespace KIGFX;
+using namespace KIGFX::PREVIEW;
+
+
+SELECTION_AREA::SELECTION_AREA()
+{
+    SetStrokeColor( COLOR4D( 1.0, 1.0, 0.4, 1.0 ) );
+    SetFillColor( COLOR4D( 0.3, 0.3, 0.5, 0.3 ) );
+}
+
 
 const BOX2I SELECTION_AREA::ViewBBox() const
 {
@@ -41,26 +48,8 @@ const BOX2I SELECTION_AREA::ViewBBox() const
 }
 
 
-void SELECTION_AREA::ViewGetLayers( int aLayers[], int& aCount ) const
+void SELECTION_AREA::drawPreviewShape( KIGFX::GAL& aGal ) const
 {
-    aLayers[0] = SelectionLayer;
-    aCount = 1;
+    aGal.DrawRectangle( m_origin, m_end );
 }
 
-
-void SELECTION_AREA::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
-{
-    auto gal = aView->GetGAL();
-    gal->SetLineWidth( 1.0 );
-    gal->SetStrokeColor( COLOR4D( 1.0, 1.0, 0.4, 1.0 ) );
-    gal->SetFillColor( COLOR4D( 0.3, 0.3, 0.5, 0.3 ) );
-    gal->SetIsStroke( true );
-    gal->SetIsFill( true );
-    gal->DrawRectangle( m_origin, m_end );
-}
-
-
-SELECTION_AREA::SELECTION_AREA() :
-    EDA_ITEM( NOT_USED )    // this item is never added to a BOARD so it needs no type.
-{
-}
diff --git a/pcbnew/tools/selection_area.h b/include/preview_items/selection_area.h
similarity index 65%
rename from pcbnew/tools/selection_area.h
rename to include/preview_items/selection_area.h
index 97fb0ab02..9c274c86f 100644
--- a/pcbnew/tools/selection_area.h
+++ b/include/preview_items/selection_area.h
@@ -22,63 +22,70 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-#ifndef __SELECTION_AREA_H
-#define __SELECTION_AREA_H
+#ifndef PREVIEW_ITEMS_SELECTION_AREA_H
+#define PREVIEW_ITEMS_SELECTION_AREA_H
+
+#include <preview_items/simple_overlay_item.h>
 
-#include <base_struct.h>
-#include <layers_id_colors_and_visibility.h>
-#include <math/box2.h>
 
 namespace KIGFX
 {
 class GAL;
-}
+
+namespace PREVIEW
+{
 
 /**
  * Class SELECTION_AREA
  *
- * Represents a selection area (currently a rectangle) in a VIEW.
+ * Represents a selection area (currently a rectangle) in a VIEW,
+ * drawn corner-to-corner between two points. This is useful when
+ * selecting a rectangular area, for lasso-select or zooming, for
+ * example.
  */
-class SELECTION_AREA : public EDA_ITEM
+class SELECTION_AREA : public SIMPLE_OVERLAY_ITEM
 {
 public:
-    static const int SelectionLayer = ITEM_GAL_LAYER( GP_OVERLAY );
 
     SELECTION_AREA();
-    ~SELECTION_AREA() {};
-
-    virtual const BOX2I ViewBBox() const override;
 
-    void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override;
-
-    void ViewGetLayers( int aLayers[], int& aCount ) const override;
+    const BOX2I ViewBBox() const override;
 
+    ///> Set the origin of the rectange (the fixed corner)
     void SetOrigin( VECTOR2I aOrigin )
     {
         m_origin = aOrigin;
     }
 
+    /**
+     * Set the current end of the rectangle (the corner that moves
+     * with the cursor.
+     */
     void SetEnd( VECTOR2I aEnd )
     {
         m_end = aEnd;
     }
 
-#if defined(DEBUG)
-    void Show( int x, std::ostream& st ) const override
-    {
-    }
-#endif
-
-    /** Get class name
+    /**
+     * Get class name
      * @return  string "SELECTION_AREA"
      */
-    virtual wxString GetClass() const override
+    wxString GetClass() const override
     {
         return wxT( "SELECTION_AREA" );
     }
 
 private:
+
+    /**
+     * Draw the selection rectangle onto the GAL
+     */
+    void drawPreviewShape( KIGFX::GAL& aGal ) const override;
+
     VECTOR2I m_origin, m_end;
 };
 
-#endif
+} // PREVIEW
+} // KIGFX
+
+#endif // PREVIEW_ITEMS_SELECTION_AREA_H
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 47a70f5ce..53b8bcd7f 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -290,7 +290,6 @@ set( PCBNEW_CLASS_SRCS
     class_action_plugin.cpp
 
     tools/selection_tool.cpp
-    tools/selection_area.cpp
     tools/pcb_selection_conditions.cpp
     tools/bright_box.cpp
     tools/edit_points.cpp
diff --git a/pcbnew/tools/selection_tool.cpp b/pcbnew/tools/selection_tool.cpp
index f25f8310f..a3e1e41d4 100644
--- a/pcbnew/tools/selection_tool.cpp
+++ b/pcbnew/tools/selection_tool.cpp
@@ -43,6 +43,7 @@ using namespace std::placeholders;
 #include <class_draw_panel_gal.h>
 #include <view/view_controls.h>
 #include <view/view_group.h>
+#include <preview_items/selection_area.h>
 #include <painter.h>
 #include <bitmaps.h>
 #include <hotkeys.h>
@@ -52,7 +53,6 @@ using namespace std::placeholders;
 #include <ratsnest_data.h>
 
 #include "selection_tool.h"
-#include "selection_area.h"
 #include "bright_box.h"
 #include "pcb_actions.h"
 
@@ -464,7 +464,7 @@ bool SELECTION_TOOL::selectMultiple()
     KIGFX::VIEW* view = getView();
     getViewControls()->SetAutoPan( true );
 
-    SELECTION_AREA area;
+    KIGFX::PREVIEW::SELECTION_AREA area;
     view->Add( &area );
 
     while( OPT_TOOL_EVENT evt = Wait() )
diff --git a/pcbnew/tools/selection_tool.h b/pcbnew/tools/selection_tool.h
index 28c2fc202..aff8e499b 100644
--- a/pcbnew/tools/selection_tool.h
+++ b/pcbnew/tools/selection_tool.h
@@ -37,7 +37,6 @@
 #include <tool/tool_menu.h>
 
 class PCB_BASE_FRAME;
-class SELECTION_AREA;
 class BOARD_ITEM;
 class GENERAL_COLLECTOR;
 
diff --git a/pcbnew/tools/zoom_tool.cpp b/pcbnew/tools/zoom_tool.cpp
index 5ced5a335..0278e3402 100644
--- a/pcbnew/tools/zoom_tool.cpp
+++ b/pcbnew/tools/zoom_tool.cpp
@@ -23,9 +23,9 @@
 #include <view/view_controls.h>
 #include <view/view.h>
 #include <tool/tool_manager.h>
+#include <preview_items/selection_area.h>
 
 #include "zoom_tool.h"
-#include "selection_area.h"
 #include "pcb_actions.h"
 
 
@@ -76,7 +76,7 @@ bool ZOOM_TOOL::selectRegion()
     auto canvas = m_frame->GetGalCanvas();
     getViewControls()->SetAutoPan( true );
 
-    SELECTION_AREA area;
+    KIGFX::PREVIEW::SELECTION_AREA area;
     view->Add( &area );
 
     while( auto evt = Wait() )
-- 
2.12.0

From 1f2ff618f371c87dd0fbe0d2fcf80954cff38b5f Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Mon, 27 Feb 2017 01:45:33 +0800
Subject: [PATCH 1/3] Add a simple item class for graphical overlays

This introduces SIMPLE_OVERLAT_ITEM, which is an abstract EDA_ITEM,
designed for use as an overlay for assisting interactive tools.

The item is drawn only on the GP_OVERLAY layer, and sets the fill,
stroke and line properties before calling a virtual function to draw the
shape, which will depend on the implementaiton of the derived class.

The motivation here is to simplify and unify basic overlays used when
in interactive tools. It is not designed to be the base class of all
possible overlays - more complex ones would be more clearly represented
as their own derivative of EDA_ITEM.

Applications of this class can include: zoom/select rectangles, zone
polygon previews, geometric shape helpers, and so on.
---
 common/CMakeLists.txt                        |   5 +
 common/preview_items/simple_overlay_item.cpp |  70 ++++++++++++++
 include/preview_items/simple_overlay_item.h  | 136 +++++++++++++++++++++++++++
 3 files changed, 211 insertions(+)
 create mode 100644 common/preview_items/simple_overlay_item.cpp
 create mode 100644 include/preview_items/simple_overlay_item.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index d1f22ce7e..d2910369a 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -183,12 +183,17 @@ set( COMMON_PAGE_LAYOUT_SRCS
     page_layout/page_layout_reader.cpp
     )
 
+set( COMMON_PREVIEW_ITEMS_SRCS
+    preview_items/simple_overlay_item.cpp
+    )
+
 set( COMMON_SRCS
     ${LIB_KICAD_SRCS}
     ${COMMON_ABOUT_DLG_SRCS}
     ${COMMON_DLG_SRCS}
     ${COMMON_WIDGET_SRCS}
     ${COMMON_PAGE_LAYOUT_SRCS}
+    ${COMMON_PREVIEW_ITEMS_SRCS}
     app_signal_handler.cpp
     base_struct.cpp
     basicframe.cpp
diff --git a/common/preview_items/simple_overlay_item.cpp b/common/preview_items/simple_overlay_item.cpp
new file mode 100644
index 000000000..d8726d0ef
--- /dev/null
+++ b/common/preview_items/simple_overlay_item.cpp
@@ -0,0 +1,70 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 Kicad Developers, see change_log.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 <preview_items/simple_overlay_item.h>
+
+#include <gal/graphics_abstraction_layer.h>
+#include <view/view.h>
+#include <layers_id_colors_and_visibility.h>
+
+
+using namespace KIGFX::PREVIEW;
+
+
+SIMPLE_OVERLAY_ITEM::SIMPLE_OVERLAY_ITEM():
+    EDA_ITEM( NOT_USED ),    // this item is never added to a BOARD so it needs no type.
+    m_fillColor( WHITE ),
+    m_strokeColor( WHITE ),
+    m_lineWidth( 1.0 )
+{
+}
+
+
+void SIMPLE_OVERLAY_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
+{
+    auto& gal = *aView->GetGAL();
+
+    setupGal( gal );
+    drawPreviewShape( gal );
+}
+
+
+void SIMPLE_OVERLAY_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
+{
+    static const int SelectionLayer = ITEM_GAL_LAYER( GP_OVERLAY );
+
+    aLayers[0] = SelectionLayer;
+    aCount = 1;
+}
+
+
+void SIMPLE_OVERLAY_ITEM::setupGal( KIGFX::GAL& aGal ) const
+{
+    // default impl: set up the GAL options we have - the
+    // overriding class can add to this if needed
+    aGal.SetLineWidth( m_lineWidth );
+    aGal.SetStrokeColor( m_strokeColor );
+    aGal.SetFillColor( m_fillColor );
+    aGal.SetIsStroke( true );
+    aGal.SetIsFill( true );
+}
diff --git a/include/preview_items/simple_overlay_item.h b/include/preview_items/simple_overlay_item.h
new file mode 100644
index 000000000..b2d673df7
--- /dev/null
+++ b/include/preview_items/simple_overlay_item.h
@@ -0,0 +1,136 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 Kicad Developers, see change_log.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
+ */
+
+#ifndef PREVIEW_SIMPLE_OUTLINE_ITEM__H_
+#define PREVIEW_SIMPLE_OUTLINE_ITEM__H_
+
+#include <base_struct.h>
+
+#include <gal/color4d.h>
+
+namespace KIGFX
+{
+
+class VIEW;
+class GAL;
+
+namespace PREVIEW
+{
+
+/**
+ * SIMPLE_OVERLAY_ITEM is class that represents a visual area drawn on
+ * a canvas, used to temporarily demarcate an area or show something
+ * on an overlay. An example could be the drag select lasso box.
+ *
+ * This class is pretty generic in terms of what the area looks like.
+ * It provides a fill, stroke and width, which is probably sufficient
+ * for most simple previews, but the inheritor has freedom to override
+ * this.
+ *
+ * If this doesn't suit a particular preview, it may mean you should
+ * implement your own EDA_ITEM derivative rather than inheriting this.
+ */
+class SIMPLE_OVERLAY_ITEM : public EDA_ITEM
+{
+public:
+
+    SIMPLE_OVERLAY_ITEM();
+
+    /**
+     * Sets the overlay layer only. You can override this if
+     * you have more layers to draw on.
+     */
+    void ViewGetLayers( int aLayers[], int& aCount ) const override;
+
+    /**
+     * Draws the preview - this is done by calling the two functions:
+     * setupGal() and drawPreviewShape(). If you need more than this,
+     * or direct access to the VIEW, you probably should make a new
+     * class.
+     */
+    void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
+
+
+#if defined(DEBUG)
+    void Show( int x, std::ostream& st ) const override
+    {
+    }
+#endif
+
+    /**
+     * Get class name
+     * @return  string "SIMPLE_OVERLAY_ITEM"
+     */
+    virtual wxString GetClass() const override
+    {
+        return "SIMPLE_OVERLAY_ITEM";
+    }
+
+    ///> Set the stroke colour to set before drawing preview
+    void SetStrokeColor( const COLOR4D& aNewColor )
+    {
+        m_strokeColor = aNewColor;
+    }
+
+    ///> Set the fill colour to set before drawing preview
+    void SetFillColor( const COLOR4D& aNewColor )
+    {
+        m_fillColor = aNewColor;
+    }
+
+    ///> Set the line width to set before drawing preview
+    void SetLineWidth( double aNewWidth )
+    {
+        m_lineWidth = aNewWidth;
+    }
+
+private:
+
+    /**
+     * Set up the GAL canvas - this provides a default implementation,
+     * that sets fill, stroke and width.
+     * If that's not suitable, you can set more options in
+     * updatePreviewShape(), but you might find that defining a new
+     * EDA_ITEM derivative is easier for heavily customised cases.
+     */
+    void setupGal( KIGFX::GAL& aGal ) const;
+
+    /**
+     * Draw the preview onto the given GAL. setupGal() will be called
+     * before this function.
+     *
+     * Subclasses should implement this in terms of their own graphical
+     * data.
+     */
+    virtual void drawPreviewShape( KIGFX::GAL& aGal ) const = 0;
+
+
+    COLOR4D m_fillColor;
+    COLOR4D m_strokeColor;
+    double  m_lineWidth;
+};
+
+} // PREVIEW
+} // KIGFX
+
+#endif  // PREVIEW_SIMPLE_OUTLINE_ITEM__H_
-- 
2.12.0


Follow ups