From 8c3b8ee69306fbd428c5be9cf12d1372967ad90b Mon Sep 17 00:00:00 2001 From: John Beard Date: Thu, 9 Mar 2017 17:09:13 +0800 Subject: [PATCH] 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 | 177 +++++++++++++++++ common/preview_items/ruler_item.cpp | 261 +++++++++++++++++++++++++ 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, 934 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 8dae66fcf4..80bdcea45d 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 0000000000..8031f1fbfb --- /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 + +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 0000000000..f821f10375 --- /dev/null +++ b/bitmaps_png/sources/measurement.svg @@ -0,0 +1,140 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 4520919b10..ce319cd899 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 0000000000..6cef9059b6 --- /dev/null +++ b/common/preview_items/preview_utils.cpp @@ -0,0 +1,177 @@ +/* + * 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 + +#include + +#include + +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, + const std::vector& 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( const 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 0000000000..331fc45fbd --- /dev/null +++ b/common/preview_items/ruler_item.cpp @@ -0,0 +1,261 @@ +/* + * 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 + +#include +#include +#include +#include + +#include +#include + +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 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 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 8f63a84496..e58ade38d4 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 0000000000..c4c711b60f --- /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 +#include +#include + +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, + const std::vector& 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 0000000000..ea32d83c6b --- /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 + +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 8d1fa6dc45..22307a8864 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 2a69f0ddcb..ddf96811ea 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 60ad2bc816..4bf4c4ae79 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 337c3f0831..2255d1f59a 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 a43a43ab5c..38b9ef08f0 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 bfbe39d4a9..4927fdc160 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 eced5cb960..e092e9c8f2 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 a7953ae9f4..e289560307 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 distance between two points" ), + wxITEM_CHECK ); + m_drawToolBar->Realize(); } diff --git a/pcbnew/tool_pcb.cpp b/pcbnew/tool_pcb.cpp index 0750e9ffcd..d51bd21b07 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 ), + _( "Measure distance between two points" ), + wxITEM_CHECK ); + m_drawToolBar->Realize(); } diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index b26aa4b553..932065ca45 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,8 @@ using namespace std::placeholders; #include +#include + #include // 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 distance 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 distance 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 8df6122924..afd2f12094 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 a4fb3301cf..486450f9fa 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -151,6 +151,10 @@ boost::optional 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 25f8fd5e8a..21615c7ff0 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;