diff --git a/AUTHORS.txt b/AUTHORS.txt index 103047ca8c..2129a8113c 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -41,6 +41,7 @@ Mateusz Skowroński Cheng Sheng Google Inc. Kristoffer Ödmark Oliver Walters +Jon Evans See also CHANGELOG.txt for contributors. diff --git a/gerbview/CMakeLists.txt b/gerbview/CMakeLists.txt index be12ffae31..8a6a71b038 100644 --- a/gerbview/CMakeLists.txt +++ b/gerbview/CMakeLists.txt @@ -63,6 +63,14 @@ set( GERBVIEW_SRCS rs274x.cpp select_layers_to_pcb.cpp toolbars_gerber.cpp + + gerbview_draw_panel_gal.cpp + gerbview_painter.cpp + + tools/gerbview_actions.cpp + tools/selection_tool.cpp + tools/gerbview_control.cpp + gerber_collectors.cpp ) set( GERBVIEW_EXTRA_SRCS diff --git a/gerbview/gerber_collectors.cpp b/gerbview/gerber_collectors.cpp new file mode 100644 index 0000000000..639fcab5ea --- /dev/null +++ b/gerbview/gerber_collectors.cpp @@ -0,0 +1,71 @@ +/* + * 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 3 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, see . + */ + +#include "gerber_collectors.h" + +const KICAD_T GERBER_COLLECTOR::AllItems[] = { + GERBER_IMAGE_LIST_T, + GERBER_IMAGE_T, + GERBER_DRAW_ITEM_T, + EOT +}; + + +/** + * Function Inspect + * is the examining function within the INSPECTOR which is passed to the + * Iterate function. Searches and collects all the objects that the old + * function PcbGeneralLocateAndDisplay() would find, except that it keeps all + * that it finds and does not do any displaying. + * + * @param testItem An EDA_ITEM to examine. + * @param testData not used here. + * @return SEARCH_RESULT - SEARCH_QUIT if the Iterator is to stop the scan, + * else SCAN_CONTINUE; + */ +SEARCH_RESULT GERBER_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData ) +{ + if( testItem->HitTest( m_RefPos ) ) + Append( testItem ); + + return SEARCH_CONTINUE; +} + + +void GERBER_COLLECTOR::Collect( EDA_ITEM* aItem, const KICAD_T aScanList[], + const wxPoint& aRefPos/*, const COLLECTORS_GUIDE& aGuide*/ ) +{ + Empty(); // empty the collection, primary criteria list + + // remember guide, pass it to Inspect() + //SetGuide( &aGuide ); + + SetScanTypes( aScanList ); + + // remember where the snapshot was taken from and pass refPos to + // the Inspect() function. + SetRefPos( aRefPos ); + + aItem->Visit( m_inspector, NULL, m_ScanTypes ); + + SetTimeNow(); // when snapshot was taken + + // record the length of the primary list before concatenating on to it. + m_PrimaryLength = m_List.size(); +} diff --git a/gerbview/gerber_collectors.h b/gerbview/gerber_collectors.h new file mode 100644 index 0000000000..64714a9b70 --- /dev/null +++ b/gerbview/gerber_collectors.h @@ -0,0 +1,134 @@ +/* + * 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 3 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, see . + */ + +#ifndef GERBER_COLLECTORS_H +#define GERBER_COLLECTORS_H + +#include + +/** + * Class GERBER_COLLECTOR + * is intended for use when the right click button is pressed, or when the + * plain "arrow" tool is in effect. + */ +class GERBER_COLLECTOR : public COLLECTOR +{ +protected: + /** + * A place to hold collected objects which don't match precisely the search + * criteria, but would be acceptable if nothing else is found. + * "2nd" choice, which will be appended to the end of COLLECTOR's prime + * "list" at the end of the search. + */ + std::vector m_List2nd; + + + /** + * Determines which items are to be collected by Inspect() + */ + //const COLLECTORS_GUIDE* m_Guide; + + + /** + * The number of items that were originally in the primary list before the + * m_List2nd was concatenated onto the end of it. + */ + int m_PrimaryLength; + + +public: + + /** + * A scan list for all selectable gerber items + */ + static const KICAD_T AllItems[]; + + GERBER_COLLECTOR() + { + //m_Guide = NULL; + m_PrimaryLength = 0; + SetScanTypes( AllItems ); + } + + void Empty2nd() + { + m_List2nd.clear(); + } + + /*void Append2nd( BOARD_ITEM* item ) + { + m_List2nd.push_back( item ); + }*/ + + + /** + * Function SetGuide + * records which COLLECTORS_GUIDE to use. + * @param aGuide Which guide to use in the collection. + */ + //void SetGuide( const COLLECTORS_GUIDE* aGuide ) { m_Guide = aGuide; } + + + /** + * Function operator[int] + * overloads COLLECTOR::operator[](int) to return a EDA_ITEM* instead of + * an EDA_ITEM* type. + * @param ndx The index into the list. + * @return EDA_ITEM* - or something derived from it, or NULL. + */ + EDA_ITEM* operator[]( int ndx ) const + { + if( (unsigned)ndx < (unsigned)GetCount() ) + return (EDA_ITEM*) m_List[ ndx ]; + return NULL; + } + + /** + * Function GetPrimaryCount + * @return int - The number if items which met the primary search criteria + */ + int GetPrimaryCount() { return m_PrimaryLength; } + + /** + * Function Inspect + * is the examining function within the INSPECTOR which is passed to the + * Iterate function. + * + * @param testItem An EDA_ITEM to examine. + * @param testData is not used in this class. + * @return SEARCH_RESULT - SEARCH_QUIT if the Iterator is to stop the scan, + * else SCAN_CONTINUE; + */ + SEARCH_RESULT Inspect( EDA_ITEM* testItem, void* testData ) override; + + /** + * Function Collect + * scans an EDA_ITEM using this class's Inspector method, which does the collection. + * @param aItem An EDA_ITEM to scan + * @param aScanList A list of KICAD_Ts with a terminating EOT, that specs + * what is to be collected and the priority order of the resultant + * collection in "m_List". + * @param aRefPos A wxPoint to use in hit-testing. + * @param aGuide The COLLECTORS_GUIDE to use in collecting items. + */ + void Collect( EDA_ITEM* aItem, const KICAD_T aScanList[], + const wxPoint& aRefPos/*, const COLLECTORS_GUIDE& aGuide */); +}; + +#endif diff --git a/gerbview/gerbview_draw_panel_gal.cpp b/gerbview/gerbview_draw_panel_gal.cpp new file mode 100644 index 0000000000..69e930019d --- /dev/null +++ b/gerbview/gerbview_draw_panel_gal.cpp @@ -0,0 +1,154 @@ +/* + * 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 3 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, see . + */ + +#include "gerbview_draw_panel_gal.h" +#include +#include +#include + +#include +#include +#include +#include + +#include +using namespace std::placeholders; + + +GERBVIEW_DRAW_PANEL_GAL::GERBVIEW_DRAW_PANEL_GAL( wxWindow* aParentWindow, wxWindowID aWindowId, + const wxPoint& aPosition, const wxSize& aSize, + KIGFX::GAL_DISPLAY_OPTIONS& aOptions, GAL_TYPE aGalType ) : +EDA_DRAW_PANEL_GAL( aParentWindow, aWindowId, aPosition, aSize, aOptions, aGalType ) +{ + setDefaultLayerDeps(); + + m_painter = new KIGFX::GERBVIEW_PAINTER( m_gal ); + m_view->SetPainter( m_painter ); + + // Load display options (such as filled/outline display of items). + auto frame = static_cast< GERBVIEW_FRAME* >( GetParentEDAFrame() ); + + if( frame ) + { + auto displ_opts = (GBR_DISPLAY_OPTIONS*) frame->GetDisplayOptions(); + static_cast( m_view->GetPainter()->GetSettings() )->LoadDisplayOptions( displ_opts ); + UseColorScheme( frame->m_colorsSettings ); + } +} + + +GERBVIEW_DRAW_PANEL_GAL::~GERBVIEW_DRAW_PANEL_GAL() +{ +} + + +void GERBVIEW_DRAW_PANEL_GAL::UseColorScheme( const COLORS_DESIGN_SETTINGS* aSettings ) +{ + KIGFX::GERBVIEW_RENDER_SETTINGS* rs; + rs = static_cast( m_view->GetPainter()->GetSettings() ); + rs->ImportLegacyColors( aSettings ); +} + + +void GERBVIEW_DRAW_PANEL_GAL::SetHighContrastLayer( int aLayer ) +{ + // Set display settings for high contrast mode + KIGFX::RENDER_SETTINGS* rSettings = m_view->GetPainter()->GetSettings(); + + SetTopLayer( aLayer ); + + rSettings->ClearActiveLayers(); + rSettings->SetActiveLayer( aLayer ); + rSettings->SetActiveLayer( GERBER_DCODE_LAYER( aLayer ) ); + + m_view->UpdateAllLayersColor(); +} + + +void GERBVIEW_DRAW_PANEL_GAL::GetMsgPanelInfo( std::vector& aList ) +{ + +} + + +void GERBVIEW_DRAW_PANEL_GAL::OnShow() +{ + GERBVIEW_FRAME* frame = dynamic_cast( GetParent() ); + + if( frame ) + { + SetTopLayer( frame->GetActiveLayer() ); + GBR_DISPLAY_OPTIONS* displ_opts = (GBR_DISPLAY_OPTIONS*) frame->GetDisplayOptions(); + static_cast( + m_view->GetPainter()->GetSettings() )->LoadDisplayOptions( displ_opts ); + } + + m_view->RecacheAllItems(); +} + + +bool GERBVIEW_DRAW_PANEL_GAL::SwitchBackend( GAL_TYPE aGalType ) +{ + bool rv = EDA_DRAW_PANEL_GAL::SwitchBackend( aGalType ); + setDefaultLayerDeps(); + return rv; +} + + +void GERBVIEW_DRAW_PANEL_GAL::setDefaultLayerDeps() +{ + // caching makes no sense for Cairo and other software renderers + auto target = m_backend == GAL_TYPE_OPENGL ? KIGFX::TARGET_CACHED : KIGFX::TARGET_NONCACHED; + + for( int i = 0; i < KIGFX::VIEW::VIEW_MAX_LAYERS; i++ ) + m_view->SetLayerTarget( i, target ); + + // for( int i = GERBVIEW_LAYER_ID_START; i < GERBVIEW_LAYER_ID_RESERVED; i++ ) + // m_view->SetLayerDisplayOnly( i ); + + m_view->SetLayerDisplayOnly( LAYER_DCODES ); + m_view->SetLayerDisplayOnly( LAYER_NEGATIVE_OBJECTS ); + m_view->SetLayerDisplayOnly( LAYER_GERBVIEW_GRID ); + m_view->SetLayerDisplayOnly( LAYER_GERBVIEW_AXES ); + m_view->SetLayerDisplayOnly( LAYER_GERBVIEW_BACKGROUND ); + + m_view->SetLayerTarget( LAYER_GP_OVERLAY , KIGFX::TARGET_OVERLAY ); + m_view->SetLayerDisplayOnly( LAYER_GP_OVERLAY ); +} + + +void GERBVIEW_DRAW_PANEL_GAL::SetTopLayer( int aLayer ) +{ + m_view->ClearTopLayers(); + + for( int i = 0; i < GERBER_DRAWLAYERS_COUNT; ++i ) + { + m_view->SetLayerOrder( GERBER_DCODE_LAYER( GERBER_DRAW_LAYER( i ) ), 2 * i ); + m_view->SetLayerOrder( i, ( 2 * i ) + 1 ); + } + + m_view->SetTopLayer( aLayer ); + + // Move DCODE layer to the top + m_view->SetTopLayer( GERBER_DCODE_LAYER( aLayer ) ); + + m_view->SetTopLayer( LAYER_GP_OVERLAY ); + + m_view->UpdateAllLayersOrder(); +} diff --git a/gerbview/gerbview_draw_panel_gal.h b/gerbview/gerbview_draw_panel_gal.h new file mode 100644 index 0000000000..c83def2596 --- /dev/null +++ b/gerbview/gerbview_draw_panel_gal.h @@ -0,0 +1,64 @@ +/* + * 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 3 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, see . + */ + +#ifndef GERBVIEW_DRAW_PANEL_GAL_H_ +#define GERBVIEW_DRAW_PANEL_GAL_H_ + +#include + +class COLORS_DESIGN_SETTINGS; + + +class GERBVIEW_DRAW_PANEL_GAL : public EDA_DRAW_PANEL_GAL +{ +public: + GERBVIEW_DRAW_PANEL_GAL( wxWindow* aParentWindow, wxWindowID aWindowId, const wxPoint& aPosition, + const wxSize& aSize, KIGFX::GAL_DISPLAY_OPTIONS& aOptions, + GAL_TYPE aGalType = GAL_TYPE_OPENGL ); + + virtual ~GERBVIEW_DRAW_PANEL_GAL(); + + /** + * Function UseColorScheme + * Applies layer color settings. + * @param aSettings are the new settings. + */ + void UseColorScheme( const COLORS_DESIGN_SETTINGS* aSettings ); + + ///> @copydoc EDA_DRAW_PANEL_GAL::SetHighContrastLayer() + virtual void SetHighContrastLayer( int aLayer ) override; + + ///> @copydoc EDA_DRAW_PANEL_GAL::GetMsgPanelInfo() + void GetMsgPanelInfo( std::vector& aList ) override; + + ///> @copydoc EDA_DRAW_PANEL_GAL::OnShow() + void OnShow() override; + + bool SwitchBackend( GAL_TYPE aGalType ) override; + + ///> @copydoc EDA_DRAW_PANEL_GAL::SetTopLayer + virtual void SetTopLayer( int aLayer ) override; + +protected: + ///> Sets rendering targets & dependencies for layers. + void setDefaultLayerDeps(); +}; + + +#endif /* GERBVIEW_DRAW_PANEL_GAL_H_ */ diff --git a/gerbview/gerbview_painter.cpp b/gerbview/gerbview_painter.cpp new file mode 100644 index 0000000000..bc95af6b70 --- /dev/null +++ b/gerbview/gerbview_painter.cpp @@ -0,0 +1,563 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace KIGFX; + +GERBVIEW_RENDER_SETTINGS::GERBVIEW_RENDER_SETTINGS() +{ + m_backgroundColor = COLOR4D( 0.0, 0.0, 0.0, 1.0 ); + + m_spotFill = true; + m_lineFill = true; + m_polygonFill = true; + m_showNegativeItems = false; + m_showCodes = false; + m_diffMode = true; + + m_componentHighlightString = ""; + m_netHighlightString = ""; + m_attributeHighlightString = ""; + + update(); +} + + +void GERBVIEW_RENDER_SETTINGS::ImportLegacyColors( const COLORS_DESIGN_SETTINGS* aSettings ) +{ + for( int i = GERBVIEW_LAYER_ID_START; + i < GERBVIEW_LAYER_ID_START + GERBER_DRAWLAYERS_COUNT; i++ ) + { + COLOR4D baseColor = aSettings->GetLayerColor( i ); + m_layerColors[i] = baseColor; + m_layerColorsHi[i] = baseColor.Brightened( 0.5 ); + m_layerColorsSel[i] = baseColor.Brightened( 0.8 ); + m_layerColorsDark[i] = baseColor.Darkened( 0.25 ); + } + + for( int i = LAYER_DCODES; i < GERBVIEW_LAYER_ID_END; i++ ) + m_layerColors[i] = aSettings->GetLayerColor( i ); + + for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ ) + m_layerColors[i] = aSettings->GetLayerColor( i ); + + update(); +} + + +void GERBVIEW_RENDER_SETTINGS::LoadDisplayOptions( const GBR_DISPLAY_OPTIONS* aOptions ) +{ + if( aOptions == NULL ) + return; + + m_spotFill = aOptions->m_DisplayFlashedItemsFill; + m_lineFill = aOptions->m_DisplayLinesFill; + m_polygonFill = aOptions->m_DisplayPolygonsFill; + m_showNegativeItems = aOptions->m_DisplayNegativeObjects; + m_showCodes = aOptions->m_DisplayDCodes; + m_diffMode = aOptions->m_DiffMode; + m_hiContrastEnabled = aOptions->m_HighContrastMode; + + update(); +} + + +const COLOR4D& GERBVIEW_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const +{ + const GERBER_DRAW_ITEM* item = static_cast( aItem ); + + // All DCODE layers stored under a single color setting + if( IsDCodeLayer( aLayer ) ) + return m_layerColors[ LAYER_DCODES ]; + + if( item ) + { + if( item->IsSelected() ) + return m_layerColorsSel[aLayer]; + } + + if( !m_netHighlightString.IsEmpty() && + m_netHighlightString == item->GetNetAttributes().m_Netname ) + return m_layerColorsHi[aLayer]; + + if( !m_componentHighlightString.IsEmpty() && + m_componentHighlightString == item->GetNetAttributes().m_Cmpref ) + return m_layerColorsHi[aLayer]; + + if( !m_attributeHighlightString.IsEmpty() && item->GetDcodeDescr() && + m_attributeHighlightString == item->GetDcodeDescr()->m_AperFunction ) + return m_layerColorsHi[aLayer]; + + // Return grayish color for non-highlighted layers in the high contrast mode + if( m_hiContrastEnabled && m_activeLayers.count( aLayer ) == 0) + return m_hiContrastColor; + + // Catch the case when highlight and high-contraste modes are enabled + // and we are drawing a not highlighted track + if( m_highlightEnabled ) + return m_layerColorsDark[aLayer]; + + // No special modificators enabled + return m_layerColors[aLayer]; +} + + +GERBVIEW_PAINTER::GERBVIEW_PAINTER( GAL* aGal ) : + PAINTER( aGal ) +{ +} + + +// TODO(JE): Pull up to PAINTER? +int GERBVIEW_PAINTER::getLineThickness( int aActualThickness ) const +{ + // if items have 0 thickness, draw them with the outline + // width, otherwise respect the set value (which, no matter + // how small will produce something) + if( aActualThickness == 0 ) + return m_gerbviewSettings.m_outlineWidth; + + return aActualThickness; +} + + +bool GERBVIEW_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer ) +{ + const EDA_ITEM* item = static_cast( aItem ); + + // the "cast" applied in here clarifies which overloaded draw() is called + switch( item->Type() ) + { + case GERBER_DRAW_ITEM_T: + draw( static_cast( const_cast( item ) ), aLayer ); + break; + + default: + // Painter does not know how to draw the object + return false; + } + + return true; +} + + +// TODO(JE) aItem can't be const because of GetDcodeDescr() +// Probably that can be refactored in GERBER_DRAW_ITEM to allow const here. +void GERBVIEW_PAINTER::draw( /*const*/ GERBER_DRAW_ITEM* aItem, int aLayer ) +{ + VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) ); // TODO(JE) Getter + VECTOR2D end( aItem->GetABPosition( aItem->m_End ) ); // TODO(JE) Getter + int width = aItem->m_Size.x; // TODO(JE) Getter + bool isFilled = true; + COLOR4D color; + // TODO(JE) This doesn't actually work properly for ImageNegative + bool isNegative = (aItem->GetLayerPolarity() ^ aItem->m_GerberImageFile->m_ImageNegative); + + // Draw DCODEs if enabled + if( IsDCodeLayer( aLayer ) ) + { + if( !m_gerbviewSettings.m_showCodes ) + return; + + wxString codeText; + VECTOR2D textPosition; + double textSize; + + if( aItem->GetDcodeDescr() ) + textSize = aItem->GetDcodeDescr()->GetShapeDim( aItem ) / 3.0; + else + textSize = std::min( aItem->m_Size.x, aItem->m_Size.y ) / 2.0; + + if( aItem->m_Shape == GBR_ARC ) + { + textPosition = start; + } + else if( aItem->m_Flashed ) + { + BOX2I bb = aItem->ViewBBox(); + textPosition = bb.Centre(); + } + else + { + + textPosition.x = (start.x + end.x) / 2; + textPosition.y = (start.y + end.y) / 2; + } + + color = m_gerbviewSettings.GetColor( aItem, aLayer ); + codeText.Printf( wxT( "D%d" ), aItem->m_DCode ); + + m_gal->SetIsStroke( true ); + m_gal->SetIsFill( false ); + m_gal->SetStrokeColor( color ); + m_gal->SetFillColor( COLOR4D( 0, 0, 0, 0 ) ); + m_gal->SetLineWidth( 2 ); + m_gal->SetFontBold( false ); + m_gal->SetFontItalic( false ); + m_gal->SetTextMirrored( false ); + m_gal->SetGlyphSize( VECTOR2D( textSize, textSize) ); + m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER ); + m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER ); + m_gal->BitmapText( codeText, textPosition, 0 ); + + return; + } + + color = m_gerbviewSettings.GetColor( aItem, aLayer ); + + if( isNegative ) + { + if( m_gerbviewSettings.m_showNegativeItems ) + color = m_gerbviewSettings.GetLayerColor( LAYER_NEGATIVE_OBJECTS ); + else + color = COLOR4D( 0, 0, 0, 0 ); + } + else if( m_gerbviewSettings.m_diffMode ) + { + color.a = 0.75; + } + + m_gal->SetNegativeDrawMode( isNegative ); + m_gal->SetStrokeColor( color ); + m_gal->SetFillColor( color ); + m_gal->SetIsFill( isFilled ); + m_gal->SetIsStroke( !isFilled ); + + switch( aItem->m_Shape ) + { + case GBR_POLYGON: + { + isFilled = m_gerbviewSettings.m_polygonFill; + m_gal->SetIsFill( isFilled ); + m_gal->SetIsStroke( !isFilled ); + + if( isNegative && !isFilled ) + { + m_gal->SetNegativeDrawMode( false ); + m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) ); + } + + if( !isFilled ) + m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth ); + + SHAPE_POLY_SET absolutePolygon = aItem->m_Polygon; + + for( auto it = absolutePolygon.Iterate( 0 ); it; ++it ) + *it = aItem->GetABPosition( *it ); + + if( !isFilled ) + m_gal->DrawPolyline( absolutePolygon.COutline( 0 ) ); + else + m_gal->DrawPolygon( absolutePolygon ); + break; + } + + case GBR_CIRCLE: + { + isFilled = m_gerbviewSettings.m_lineFill; + double radius = GetLineLength( aItem->m_Start, aItem->m_End ); + m_gal->DrawCircle( start, radius ); + break; + } + + case GBR_ARC: + { + isFilled = m_gerbviewSettings.m_lineFill; + + // Gerber arcs are 3-point (start, center, end) + // GAL needs center, radius, start angle, end angle + double radius = GetLineLength( aItem->m_Start, aItem->m_ArcCentre ); + VECTOR2D center = aItem->GetABPosition( aItem->m_ArcCentre ); + VECTOR2D startVec = VECTOR2D( aItem->GetABPosition( aItem->m_Start ) ) - center; + VECTOR2D endVec = VECTOR2D( aItem->GetABPosition( aItem->m_End ) ) - center; + + m_gal->SetIsFill( isFilled ); + m_gal->SetIsStroke( !isFilled ); + m_gal->SetLineWidth( isFilled ? width : m_gerbviewSettings.m_outlineWidth ); + + double startAngle = startVec.Angle(); + double endAngle = endVec.Angle(); + + if( endAngle >= M_PI ) + endAngle *= -1; + + // 360-degree arcs are stored in the file with start equal to end + if( aItem->m_Start == aItem->m_End ) + { + startAngle = 0; + endAngle = 2 * M_PI; + } + + m_gal->DrawArcSegment( center, radius, startAngle, endAngle, width ); + + // Arc Debugging + // m_gal->SetLineWidth( 5 ); + // m_gal->SetStrokeColor( COLOR4D( 0.0, 1.0, 0.0, 1.0 ) ); + // m_gal->DrawLine( center, aItem->GetABPosition( aItem->m_Start ) ); + // m_gal->SetStrokeColor( COLOR4D( 1.0, 0.0, 0.0, 1.0 ) ); + // m_gal->DrawLine( center, aItem->GetABPosition( aItem->m_End ) ); + break; + } + + case GBR_SPOT_CIRCLE: + case GBR_SPOT_RECT: + case GBR_SPOT_OVAL: + case GBR_SPOT_POLY: + case GBR_SPOT_MACRO: + { + isFilled = m_gerbviewSettings.m_spotFill; + drawFlashedShape( aItem, isFilled ); + break; + } + + case GBR_SEGMENT: + { + /* Plot a line from m_Start to m_End. + * Usually, a round pen is used, but some gerber files use a rectangular pen + * In fact, any aperture can be used to plot a line. + * currently: only a square pen is handled (I believe using a polygon gives a strange plot). + */ + isFilled = m_gerbviewSettings.m_lineFill; + m_gal->SetIsFill( isFilled ); + m_gal->SetIsStroke( !isFilled ); + + if( isNegative && !isFilled ) + m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) ); + + // TODO(JE) Refactor this to allow const aItem + D_CODE* code = aItem->GetDcodeDescr(); + if( code && code->m_Shape == APT_RECT ) + { + if( aItem->m_Polygon.OutlineCount() == 0 ) + aItem->ConvertSegmentToPolygon(); + drawPolygon( aItem, aItem->m_Polygon, isFilled ); + } + else + { + if( !isFilled ) + m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth ); + + m_gal->DrawSegment( start, end, width ); + } + break; + } + + default: + wxASSERT_MSG( false, wxT( "GERBER_DRAW_ITEM shape is unknown!" ) ); + break; + } + + // Enable for bounding box debugging + #if 0 + const BOX2I& bb = aItem->ViewBBox(); + m_gal->SetIsStroke( true ); + m_gal->SetIsFill( true ); + m_gal->SetLineWidth( 3 ); + m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) ); + m_gal->SetFillColor( COLOR4D(0.9, 0.9, 0, 0.1) ); + m_gal->DrawRectangle( bb.GetOrigin(), bb.GetEnd() ); + #endif +} + + +void GERBVIEW_PAINTER::drawPolygon( GERBER_DRAW_ITEM* aParent, + SHAPE_POLY_SET aPolygon, + bool aFilled ) +{ + for( auto it = aPolygon.Iterate( 0 ); it; ++it ) + *it = aParent->GetABPosition( *it ); + + if( !m_gerbviewSettings.m_polygonFill ) + m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth ); + + if( !aFilled ) + { + for( int i = 0; i < aPolygon.OutlineCount(); i++ ) + m_gal->DrawPolyline( aPolygon.COutline( i ) ); + } + else + m_gal->DrawPolygon( aPolygon ); +} + + +void GERBVIEW_PAINTER::drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled ) +{ + D_CODE* code = aItem->GetDcodeDescr(); + + wxASSERT_MSG( code, wxT( "drawFlashedShape: Item has no D_CODE!" ) ); + + m_gal->SetIsFill( aFilled ); + m_gal->SetIsStroke( !aFilled ); + m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth ); + + switch( aItem->m_Shape ) + { + case GBR_SPOT_CIRCLE: + { + int radius = code->m_Size.x >> 1; + VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) ); + + if( !aFilled ) + { + m_gal->DrawCircle( start, radius ); + } + else + { + if( code->m_DrillShape == APT_DEF_NO_HOLE ) + { + m_gal->DrawCircle( start, radius ); + } + else // rectangular hole + { + if( code->m_Polygon.OutlineCount() == 0 ) + code->ConvertShapeToPolygon(); + + SHAPE_POLY_SET poly = code->m_Polygon; + poly.Move( aItem->m_Start ); + + drawPolygon( aItem, poly, aFilled ); + } + } + break; + } + + case GBR_SPOT_RECT: + { + wxPoint codeStart; + wxPoint aShapePos = aItem->m_Start; + codeStart.x = aShapePos.x - code->m_Size.x / 2; + codeStart.y = aShapePos.y - code->m_Size.y / 2; + wxPoint codeEnd = codeStart + code->m_Size; + codeStart = aItem->GetABPosition( codeStart ); + codeEnd = aItem->GetABPosition( codeEnd ); + + if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE ) + { + m_gal->DrawRectangle( VECTOR2D( codeStart ), VECTOR2D( codeEnd ) ); + } + else + { + if( code->m_Polygon.OutlineCount() == 0 ) + code->ConvertShapeToPolygon(); + + SHAPE_POLY_SET poly = code->m_Polygon; + poly.Move( aItem->m_Start ); + + drawPolygon( aItem, poly, aFilled ); + } + break; + } + + case GBR_SPOT_OVAL: + { + int radius = 0; + + wxPoint codeStart = aItem->m_Start; + wxPoint codeEnd = aItem->m_Start; + + if( code->m_Size.x > code->m_Size.y ) // horizontal oval + { + int delta = (code->m_Size.x - code->m_Size.y) / 2; + codeStart.x -= delta; + codeEnd.x += delta; + radius = code->m_Size.y; + } + else // horizontal oval + { + int delta = (code->m_Size.y - code->m_Size.x) / 2; + codeStart.y -= delta; + codeEnd.y += delta; + radius = code->m_Size.x; + } + + codeStart = aItem->GetABPosition( codeStart ); + codeEnd = aItem->GetABPosition( codeEnd ); + + if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE ) + { + m_gal->DrawSegment( codeStart, codeEnd, radius ); + } + else + { + if( code->m_Polygon.OutlineCount() == 0 ) + code->ConvertShapeToPolygon(); + + SHAPE_POLY_SET poly = code->m_Polygon; + poly.Move( aItem->m_Start ); + + drawPolygon( aItem, poly, aFilled ); + } + break; + } + + case GBR_SPOT_POLY: + { + if( code->m_Polygon.OutlineCount() == 0 ) + code->ConvertShapeToPolygon(); + + SHAPE_POLY_SET poly = code->m_Polygon; + poly.Move( aItem->m_Start ); + + drawPolygon( aItem, poly, aFilled ); + break; + } + + case GBR_SPOT_MACRO: + drawApertureMacro( aItem, aFilled ); + break; + + default: + wxASSERT_MSG( false, wxT( "Unknown Gerber flashed shape!" ) ); + break; + } +} + + +void GERBVIEW_PAINTER::drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled ) +{ + D_CODE* code = aParent->GetDcodeDescr(); + APERTURE_MACRO* macro = code->GetMacro(); + + SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, aParent->m_Start ); + + if( !m_gerbviewSettings.m_polygonFill ) + m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth ); + + if( !aFilled ) + { + for( int i = 0; i < macroShape->OutlineCount(); i++ ) + m_gal->DrawPolyline( macroShape->COutline( i ) ); + } + else + m_gal->DrawPolygon( *macroShape ); +} + + +const double GERBVIEW_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 ); diff --git a/gerbview/gerbview_painter.h b/gerbview/gerbview_painter.h new file mode 100644 index 0000000000..b167bb49a5 --- /dev/null +++ b/gerbview/gerbview_painter.h @@ -0,0 +1,203 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#ifndef __GERBVIEW_PAINTER_H +#define __GERBVIEW_PAINTER_H + +#include +#include +#include +#include + +#include + + +class EDA_ITEM; +class COLORS_DESIGN_SETTINGS; + +class GERBER_DRAW_ITEM; +class GERBER_FILE_IMAGE; + + +namespace KIGFX +{ +class GAL; + +/** + * Class GERBVIEW_RENDER_SETTINGS + * Stores GerbView specific render settings. + */ +class GERBVIEW_RENDER_SETTINGS : public RENDER_SETTINGS +{ +public: + friend class GERBVIEW_PAINTER; + + GERBVIEW_RENDER_SETTINGS(); + + /// @copydoc RENDER_SETTINGS::ImportLegacyColors() + void ImportLegacyColors( const COLORS_DESIGN_SETTINGS* aSettings ) override; + + /** + * Function LoadDisplayOptions + * Loads settings related to display options + * @param aOptions are settings that you want to use for displaying items. + */ + void LoadDisplayOptions( const GBR_DISPLAY_OPTIONS* aOptions ); + + /// @copydoc RENDER_SETTINGS::GetColor() + virtual const COLOR4D& GetColor( const VIEW_ITEM* aItem, int aLayer ) const override; + + /** + * Function GetLayerColor + * Returns the color used to draw a layer. + * @param aLayer is the layer number. + */ + inline const COLOR4D& GetLayerColor( int aLayer ) const + { + return m_layerColors[aLayer]; + } + + /** + * Function SetLayerColor + * Changes the color used to draw a layer. + * @param aLayer is the layer number. + * @param aColor is the new color. + */ + inline void SetLayerColor( int aLayer, const COLOR4D& aColor ) + { + m_layerColors[aLayer] = aColor; + + update(); // recompute other shades of the color + } + + inline bool IsSpotFill() const + { + return m_spotFill; + } + + inline bool IsLineFill() const + { + return m_lineFill; + } + + inline bool IsPolygonFill() const + { + return m_polygonFill; + } + + inline bool IsShowNegativeItems() const + { + return m_showNegativeItems; + } + + inline bool IsShowCodes() const + { + return m_showCodes; + } + + inline bool IsDiffMode() const + { + return m_diffMode; + } + + /// If set to anything but an empty string, will highlight items with matching component + wxString m_componentHighlightString; + + /// If set to anything but an empty string, will highlight items with matching net + wxString m_netHighlightString; + + /// If set to anything but an empty string, will highlight items with matching attribute + wxString m_attributeHighlightString; + +protected: + /// Flag determining if spots should be drawn with fill + bool m_spotFill; + + /// Flag determining if lines should be drawn with fill + bool m_lineFill; + + /// Flag determining if polygons should be drawn with fill + bool m_polygonFill; + + /// Flag determining if negative items should be drawn with a "ghost" color + bool m_showNegativeItems; + + /// Flag determining if D-Codes should be drawn + bool m_showCodes; + + /// Flag determining if layers should be rendered in "diff" mode + bool m_diffMode; + + /// Maximum font size for D-Codes and other strings + static const double MAX_FONT_SIZE; +}; + + +/** + * Class GERBVIEW_PAINTER + * Contains methods for drawing GerbView-specific items. + */ +class GERBVIEW_PAINTER : public PAINTER +{ +public: + GERBVIEW_PAINTER( GAL* aGal ); + + /// @copydoc PAINTER::ApplySettings() + virtual void ApplySettings( const RENDER_SETTINGS* aSettings ) override + { + m_gerbviewSettings = *static_cast( aSettings ); + } + + /// @copydoc PAINTER::GetSettings() + virtual GERBVIEW_RENDER_SETTINGS* GetSettings() override + { + return &m_gerbviewSettings; + } + + /// @copydoc PAINTER::Draw() + virtual bool Draw( const VIEW_ITEM* aItem, int aLayer ) override; + +protected: + GERBVIEW_RENDER_SETTINGS m_gerbviewSettings; + + // Drawing functions + void draw( /*const*/ GERBER_DRAW_ITEM* aVia, int aLayer ); + + /// Helper routine to draw a polygon + void drawPolygon( GERBER_DRAW_ITEM* aParent, SHAPE_POLY_SET aPolygon, bool aFilled ); + + /// Helper to draw a flashed shape (aka spot) + void drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled ); + + /// Helper to draw an aperture macro shape + void drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled ); + + /** + * Function getLineThickness() + * Get the thickness to draw for a line (e.g. 0 thickness lines + * get a minimum value). + * @param aActualThickness line own thickness + * @return the thickness to draw + */ + int getLineThickness( int aActualThickness ) const; +}; +} // namespace KIGFX + +#endif /* __GERBVIEW_PAINTER_H */ diff --git a/gerbview/tools/gerbview_actions.cpp b/gerbview/tools/gerbview_actions.cpp new file mode 100644 index 0000000000..577c84ea1c --- /dev/null +++ b/gerbview/tools/gerbview_actions.cpp @@ -0,0 +1,76 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#include +#include +#include +#include + +#include "gerbview_actions.h" +#include "selection_tool.h" +#include "gerbview_control.h" + + +void GERBVIEW_ACTIONS::RegisterAllTools( TOOL_MANAGER* aToolManager ) +{ + aToolManager->RegisterTool( new COMMON_TOOLS ); + aToolManager->RegisterTool( new GERBVIEW_SELECTION_TOOL ); + aToolManager->RegisterTool( new GERBVIEW_CONTROL ); + aToolManager->RegisterTool( new ZOOM_TOOL ); +} + +boost::optional GERBVIEW_ACTIONS::TranslateLegacyId( int aId ) +{ + switch( aId ) + { + case ID_ZOOM_IN: // toolbar button "Zoom In" + return ACTIONS::zoomInCenter.MakeEvent(); + + case ID_ZOOM_OUT: // toolbar button "Zoom In" + return ACTIONS::zoomOutCenter.MakeEvent(); + + case ID_ZOOM_PAGE: // toolbar button "Fit on Screen" + return ACTIONS::zoomFitScreen.MakeEvent(); + + case ID_ZOOM_SELECTION: + return ACTIONS::zoomTool.MakeEvent(); + + case ID_TB_MEASUREMENT_TOOL: + return GERBVIEW_ACTIONS::measureTool.MakeEvent(); + + case ID_NO_TOOL_SELECTED: + return GERBVIEW_ACTIONS::selectionTool.MakeEvent(); + + case ID_HIGHLIGHT_REMOVE_ALL: + return GERBVIEW_ACTIONS::highlightClear.MakeEvent(); + + case ID_HIGHLIGHT_CMP_ITEMS: + return GERBVIEW_ACTIONS::highlightComponent.MakeEvent(); + + case ID_HIGHLIGHT_NET_ITEMS: + return GERBVIEW_ACTIONS::highlightNet.MakeEvent(); + + case ID_HIGHLIGHT_APER_ATTRIBUTE_ITEMS: + return GERBVIEW_ACTIONS::highlightAttribute.MakeEvent(); + break; + } + + return boost::optional(); +} diff --git a/gerbview/tools/gerbview_actions.h b/gerbview/tools/gerbview_actions.h new file mode 100644 index 0000000000..facc333c45 --- /dev/null +++ b/gerbview/tools/gerbview_actions.h @@ -0,0 +1,139 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#ifndef __GERBVIEW_ACTIONS_H +#define __GERBVIEW_ACTIONS_H + +#include +#include +#include + +class TOOL_EVENT; +class TOOL_MANAGER; + +/** + * Class GERBVIEW_ACTIONS + * + * Gathers all the actions that are shared by tools. The instance of GERBVIEW_ACTIONS is created + * inside of ACTION_MANAGER object that registers the actions. + */ +class GERBVIEW_ACTIONS : public ACTIONS +{ +public: + // Selection Tool + /// Activation of the selection tool + static TOOL_ACTION selectionActivate; + + /// Select a single item under the cursor position + static TOOL_ACTION selectionCursor; + + /// Clears the current selection + static TOOL_ACTION selectionClear; + + /// Selects an item (specified as the event parameter). + static TOOL_ACTION selectItem; + + /// Unselects an item (specified as the event parameter). + static TOOL_ACTION unselectItem; + + /// Activation of the edit tool + static TOOL_ACTION properties; + + static TOOL_ACTION measureTool; + + // View controls + static TOOL_ACTION zoomIn; + static TOOL_ACTION zoomOut; + static TOOL_ACTION zoomInCenter; + static TOOL_ACTION zoomOutCenter; + static TOOL_ACTION zoomCenter; + static TOOL_ACTION zoomFitScreen; + static TOOL_ACTION zoomPreset; + + // Display modes + static TOOL_ACTION zoneDisplayEnable; + static TOOL_ACTION zoneDisplayDisable; + static TOOL_ACTION zoneDisplayOutlines; + static TOOL_ACTION highContrastMode; + static TOOL_ACTION highContrastInc; + static TOOL_ACTION highContrastDec; + + // Layer control + static TOOL_ACTION layerPrev; + static TOOL_ACTION layerAlphaInc; + static TOOL_ACTION layerAlphaDec; + static TOOL_ACTION layerToggle; + + static TOOL_ACTION layerChanged; // notification + + // Grid control + static TOOL_ACTION gridFast1; + static TOOL_ACTION gridFast2; + static TOOL_ACTION gridNext; + static TOOL_ACTION gridPrev; + static TOOL_ACTION gridSetOrigin; + static TOOL_ACTION gridResetOrigin; + static TOOL_ACTION gridPreset; + + /// Cursor control with keyboard + static TOOL_ACTION cursorUp; + static TOOL_ACTION cursorDown; + static TOOL_ACTION cursorLeft; + static TOOL_ACTION cursorRight; + + static TOOL_ACTION cursorUpFast; + static TOOL_ACTION cursorDownFast; + static TOOL_ACTION cursorLeftFast; + static TOOL_ACTION cursorRightFast; + + static TOOL_ACTION cursorClick; + static TOOL_ACTION cursorDblClick; + + // Panning with keyboard + static TOOL_ACTION panUp; + static TOOL_ACTION panDown; + static TOOL_ACTION panLeft; + static TOOL_ACTION panRight; + + // Miscellaneous + static TOOL_ACTION selectionTool; + static TOOL_ACTION zoomTool; + static TOOL_ACTION panTool; + static TOOL_ACTION pickerTool; + static TOOL_ACTION resetCoords; + static TOOL_ACTION switchCursor; + static TOOL_ACTION switchUnits; + static TOOL_ACTION showHelp; + static TOOL_ACTION toBeDone; + + // Highlighting + static TOOL_ACTION highlightClear; + static TOOL_ACTION highlightNet; + static TOOL_ACTION highlightComponent; + static TOOL_ACTION highlightAttribute; + + ///> @copydoc COMMON_ACTIONS::TranslateLegacyId() + virtual boost::optional TranslateLegacyId( int aId ) override; + + ///> @copydoc COMMON_ACTIONS::RegisterAllTools() + virtual void RegisterAllTools( TOOL_MANAGER* aToolManager ) override; +}; + +#endif // __GERBVIEW_ACTIONS_H diff --git a/gerbview/tools/gerbview_control.cpp b/gerbview/tools/gerbview_control.cpp new file mode 100644 index 0000000000..511771ce42 --- /dev/null +++ b/gerbview/tools/gerbview_control.cpp @@ -0,0 +1,128 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#include +#include +#include +#include + +#include "gerbview_actions.h" +#include "gerbview_control.h" +#include "selection_tool.h" + +TOOL_ACTION GERBVIEW_ACTIONS::selectionTool( "gerbview.Control.selectionTool", + AS_GLOBAL, 0, + "", "", NULL, AF_ACTIVATE ); + +TOOL_ACTION GERBVIEW_ACTIONS::layerChanged( "gerbview.Control.layerChanged", + AS_GLOBAL, 0, + "", "", NULL, AF_NOTIFY ); + +TOOL_ACTION GERBVIEW_ACTIONS::highlightClear( "gerbview.Control.highlightClear", + AS_GLOBAL, 0, + _( "Clear Highlight" ), "" ); + +TOOL_ACTION GERBVIEW_ACTIONS::highlightNet( "gerbview.Control.highlightNet", + AS_GLOBAL, 0, + _( "Highlight Net" ), "" ); + +TOOL_ACTION GERBVIEW_ACTIONS::highlightComponent( "gerbview.Control.highlightComponent", + AS_GLOBAL, 0, + _( "Highlight Component" ), "" ); + +TOOL_ACTION GERBVIEW_ACTIONS::highlightAttribute( "gerbview.Control.highlightAttribute", + AS_GLOBAL, 0, + _( "Highlight Attribute" ), "" ); + +GERBVIEW_CONTROL::GERBVIEW_CONTROL() : + TOOL_INTERACTIVE( "gerbview.Control" ), m_frame( NULL ) +{ +} + + +GERBVIEW_CONTROL::~GERBVIEW_CONTROL() +{ +} + + +void GERBVIEW_CONTROL::Reset( RESET_REASON aReason ) +{ + m_frame = getEditFrame(); +} + + +int GERBVIEW_CONTROL::HighlightControl( const TOOL_EVENT& aEvent ) +{ + auto settings = static_cast( getView()->GetPainter() )->GetSettings(); + const auto& selection = m_toolMgr->GetTool()->GetSelection(); + GERBER_DRAW_ITEM* item = NULL; + + if( selection.Size() == 1 ) + { + item = static_cast( selection[0] ); + } + + if( aEvent.IsAction( &GERBVIEW_ACTIONS::highlightClear ) ) + { + m_frame->m_SelComponentBox->SetSelection( 0 ); + m_frame->m_SelNetnameBox->SetSelection( 0 ); + m_frame->m_SelAperAttributesBox->SetSelection( 0 ); + + settings->m_netHighlightString = ""; + settings->m_componentHighlightString = ""; + settings->m_attributeHighlightString = ""; + } + else if( item && aEvent.IsAction( &GERBVIEW_ACTIONS::highlightNet ) ) + { + auto string = item->GetNetAttributes().m_Netname; + settings->m_netHighlightString = string; + m_frame->m_SelNetnameBox->SetStringSelection( string ); + } + else if( item && aEvent.IsAction( &GERBVIEW_ACTIONS::highlightComponent ) ) + { + auto string = item->GetNetAttributes().m_Cmpref; + settings->m_componentHighlightString = string; + m_frame->m_SelComponentBox->SetStringSelection( string ); + } + else if( item && aEvent.IsAction( &GERBVIEW_ACTIONS::highlightAttribute ) ) + { + D_CODE* apertDescr = item->GetDcodeDescr(); + if( apertDescr ) + { + auto string = apertDescr->m_AperFunction; + settings->m_attributeHighlightString = string; + m_frame->m_SelAperAttributesBox->SetStringSelection( string ); + } + } + + m_frame->GetGalCanvas()->GetView()->RecacheAllItems(); + m_frame->GetGalCanvas()->Refresh(); + + return 0; +} + + +void GERBVIEW_CONTROL::setTransitions() +{ + Go( &GERBVIEW_CONTROL::HighlightControl, GERBVIEW_ACTIONS::highlightClear.MakeEvent() ); + Go( &GERBVIEW_CONTROL::HighlightControl, GERBVIEW_ACTIONS::highlightNet.MakeEvent() ); + Go( &GERBVIEW_CONTROL::HighlightControl, GERBVIEW_ACTIONS::highlightComponent.MakeEvent() ); + Go( &GERBVIEW_CONTROL::HighlightControl, GERBVIEW_ACTIONS::highlightAttribute.MakeEvent() ); +} diff --git a/gerbview/tools/gerbview_control.h b/gerbview/tools/gerbview_control.h new file mode 100644 index 0000000000..c8d3496d72 --- /dev/null +++ b/gerbview/tools/gerbview_control.h @@ -0,0 +1,66 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#ifndef GERBVIEW_CONTROL_H +#define GERBVIEW_CONTROL_H + +#include + + +/** + * Class PCBNEW_CONTROL + * + * Handles actions that are shared between different frames in pcbnew. + */ + +class GERBVIEW_CONTROL : public TOOL_INTERACTIVE +{ +public: + GERBVIEW_CONTROL(); + ~GERBVIEW_CONTROL(); + + /// @copydoc TOOL_INTERACTIVE::Reset() + void Reset( RESET_REASON aReason ) override; + + // Display modes + int HighContrastMode( const TOOL_EVENT& aEvent ); + int HighContrastInc( const TOOL_EVENT& aEvent ); + int HighContrastDec( const TOOL_EVENT& aEvent ); + + // Layer control + int LayerSwitch( const TOOL_EVENT& aEvent ); + int LayerNext( const TOOL_EVENT& aEvent ); + int LayerPrev( const TOOL_EVENT& aEvent ); + int LayerToggle( const TOOL_EVENT& aEvent ); + int LayerAlphaInc( const TOOL_EVENT& aEvent ); + int LayerAlphaDec( const TOOL_EVENT& aEvent ); + + // Highlight control + int HighlightControl( const TOOL_EVENT& aEvent ); + + ///> Sets up handlers for various events. + void setTransitions() override; + +private: + GERBVIEW_FRAME* m_frame; + +}; + +#endif diff --git a/gerbview/tools/selection_tool.cpp b/gerbview/tools/selection_tool.cpp new file mode 100644 index 0000000000..9e952f4608 --- /dev/null +++ b/gerbview/tools/selection_tool.cpp @@ -0,0 +1,959 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#include + +#include +using namespace std::placeholders; + +#include + +#include +//#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "selection_tool.h" +#include "gerbview_actions.h" + +// Selection tool actions +TOOL_ACTION GERBVIEW_ACTIONS::selectionActivate( "gerbview.InteractiveSelection", + AS_GLOBAL, 0, + "", "", NULL, AF_ACTIVATE ); // No description, it is not supposed to be shown anywhere + +TOOL_ACTION GERBVIEW_ACTIONS::selectionCursor( "gerbview.InteractiveSelection.Cursor", + AS_GLOBAL, 0, + "", "" ); // No description, it is not supposed to be shown anywhere + +TOOL_ACTION GERBVIEW_ACTIONS::selectItem( "gerbview.InteractiveSelection.SelectItem", + AS_GLOBAL, 0, + "", "" ); // No description, it is not supposed to be shown anywhere + +TOOL_ACTION GERBVIEW_ACTIONS::unselectItem( "gerbview.InteractiveSelection.UnselectItem", + AS_GLOBAL, 0, + "", "" ); // No description, it is not supposed to be shown anywhere + +TOOL_ACTION GERBVIEW_ACTIONS::selectionClear( "gerbview.InteractiveSelection.Clear", + AS_GLOBAL, 0, + "", "" ); // No description, it is not supposed to be shown anywhere + +TOOL_ACTION GERBVIEW_ACTIONS::measureTool( "gerbview.InteractiveSelection.measureTool", + AS_GLOBAL, MD_CTRL + MD_SHIFT + 'M', + _( "Measure tool" ), _( "Interactively measure distance between points" ), + nullptr, AF_ACTIVATE ); + + +class HIGHLIGHT_MENU: public CONTEXT_MENU +{ +public: + HIGHLIGHT_MENU() + { + SetTitle( _( "Highlight..." ) ); + } + +private: + + void update() override + { + const auto& selection = getToolManager()->GetTool()->GetSelection(); + + if( selection.Size() == 1 ) + { + auto item = static_cast( selection[0] ); + const auto& net_attr = item->GetNetAttributes(); + + if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_PAD ) || + ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_CMP ) ) + { + auto menuEntry = Add( GERBVIEW_ACTIONS::highlightComponent ); + menuEntry->SetItemLabel( wxString::Format( _( "Highlight items of component '%s'" ), + GetChars( net_attr.m_Cmpref ) ) ); + } + + if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_NET ) ) + { + auto menuEntry = Add( GERBVIEW_ACTIONS::highlightNet ); + menuEntry->SetItemLabel( wxString::Format( _( "Highlight items of net '%s'" ), + GetChars( net_attr.m_Netname ) ) ); + } + + D_CODE* apertDescr = item->GetDcodeDescr(); + + if( apertDescr && !apertDescr->m_AperFunction.IsEmpty() ) + { + auto menuEntry = Add( GERBVIEW_ACTIONS::highlightAttribute ); + menuEntry->SetItemLabel( wxString::Format( _( "Highlight aperture type '%s'" ), + GetChars( apertDescr->m_AperFunction ) ) ); + } + } + + Add( GERBVIEW_ACTIONS::highlightClear ); + } + + CONTEXT_MENU* create() const override + { + return new HIGHLIGHT_MENU(); + } +}; + + +GERBVIEW_SELECTION_TOOL::GERBVIEW_SELECTION_TOOL() : + TOOL_INTERACTIVE( "gerbview.InteractiveSelection" ), + m_frame( NULL ), m_additive( false ), m_subtractive( false ), + m_multiple( false ), + m_menu( *this ) +{ +} + + +GERBVIEW_SELECTION_TOOL::~GERBVIEW_SELECTION_TOOL() +{ + getView()->Remove( &m_selection ); +} + + +bool GERBVIEW_SELECTION_TOOL::Init() +{ + auto selectMenu = std::make_shared(); + selectMenu->SetTool( this ); + m_menu.AddSubMenu( selectMenu ); + + auto& menu = m_menu.GetMenu(); + + menu.AddMenu( selectMenu.get(), false ); + menu.AddSeparator( SELECTION_CONDITIONS::ShowAlways, 1000 ); + + m_menu.AddStandardSubMenus( *getEditFrame() ); + + return true; +} + + +void GERBVIEW_SELECTION_TOOL::Reset( RESET_REASON aReason ) +{ + m_frame = getEditFrame(); + m_preliminary = true; + + if( aReason == TOOL_BASE::MODEL_RELOAD ) + { + // Remove pointers to the selected items from containers + // without changing their properties (as they are already deleted + // while a new file is loaded) + m_selection.Clear(); + getView()->GetPainter()->GetSettings()->SetHighlight( false ); + } + else + // Restore previous properties of selected items and remove them from containers + clearSelection(); + + // Reinsert the VIEW_GROUP, in case it was removed from the VIEW + getView()->Remove( &m_selection ); + getView()->Add( &m_selection ); +} + + +int GERBVIEW_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent ) +{ + // Main loop: keep receiving events + while( OPT_TOOL_EVENT evt = Wait() ) + { + // This is kind of hacky: activate RMB drag on any event. + // There doesn't seem to be any other good way to tell when another tool + // is canceled and control returns to the selection tool, except by the + // fact that the selection tool starts to get events again. + if( m_frame->GetToolId() == ID_NO_TOOL_SELECTED) + { + getViewControls()->SetAdditionalPanButtons( false, true ); + } + + // Disable RMB pan for other tools; they can re-enable if desired + if( evt->IsActivate() ) + { + getViewControls()->SetAdditionalPanButtons( false, false ); + } + + // single click? Select single object + if( evt->IsClick( BUT_LEFT ) ) + { + if( !m_additive ) + clearSelection(); + + selectPoint( evt->Position() ); + } + + // right click? if there is any object - show the context menu + else if( evt->IsClick( BUT_RIGHT ) ) + { + if( m_selection.Empty() ) + { + selectPoint( evt->Position() ); + m_selection.SetIsHover( true ); + } + + m_menu.ShowContextMenu( m_selection ); + } + + else if( evt->IsCancel() || evt->Action() == TA_UNDO_REDO_PRE ) + { + clearSelection(); + } + + else if( evt->Action() == TA_CONTEXT_MENU_CLOSED ) + { + m_menu.CloseContextMenu( evt ); + } + } + + // This tool is supposed to be active forever + assert( false ); + + return 0; +} + + +SELECTION& GERBVIEW_SELECTION_TOOL::GetSelection() +{ + return m_selection; +} + + +SELECTION& GERBVIEW_SELECTION_TOOL::RequestSelection( int aFlags ) +{ + if( m_selection.Empty() ) + { + m_toolMgr->RunAction( GERBVIEW_ACTIONS::selectionCursor, true, 0 ); + m_selection.SetIsHover( true ); + } + + // Be careful with iterators: items can be removed from list + // that invalidate iterators. + for( unsigned ii = 0; ii < m_selection.GetSize(); ii++ ) + { + EDA_ITEM* item = m_selection[ii]; + + if( ( aFlags & SELECTION_EDITABLE ) && item->Type() == PCB_MARKER_T ) + { + unselect( static_cast( item ) ); + } + } + + return m_selection; +} + + +void GERBVIEW_SELECTION_TOOL::toggleSelection( EDA_ITEM* aItem ) +{ + if( aItem->IsSelected() ) + { + unselect( aItem ); + + // Inform other potentially interested tools + m_toolMgr->ProcessEvent( UnselectedEvent ); + } + else + { + if( !m_additive ) + clearSelection(); + + // Prevent selection of invisible or inactive items + if( selectable( aItem ) ) + { + select( aItem ); + + // Inform other potentially interested tools + m_toolMgr->ProcessEvent( SelectedEvent ); + } + } + + m_frame->GetGalCanvas()->ForceRefresh(); +} + + +bool GERBVIEW_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag ) +{ + EDA_ITEM* item = NULL; + GERBER_COLLECTOR collector; + EDA_ITEM* model = getModel(); + + collector.Collect( model, GERBER_COLLECTOR::AllItems, wxPoint( aWhere.x, aWhere.y ) ); + + bool anyCollected = collector.GetCount() != 0; + + // Remove unselectable items + for( int i = collector.GetCount() - 1; i >= 0; --i ) + { + if( !selectable( collector[i] ) ) + collector.Remove( i ); + } + + switch( collector.GetCount() ) + { + case 0: + if( !m_additive && anyCollected ) + clearSelection(); + + return false; + + case 1: + toggleSelection( collector[0] ); + + return true; + + default: + // Let's see if there is still disambiguation in selection.. + if( collector.GetCount() == 1 ) + { + toggleSelection( collector[0] ); + + return true; + } + else if( collector.GetCount() > 1 ) + { + if( aOnDrag ) + Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) ); + + item = disambiguationMenu( &collector ); + + if( item ) + { + toggleSelection( item ); + + return true; + } + } + break; + } + + return false; +} + + +bool GERBVIEW_SELECTION_TOOL::selectCursor( bool aSelectAlways ) +{ + if( aSelectAlways || m_selection.Empty() ) + { + clearSelection(); + selectPoint( getViewControls()->GetCursorPosition( false ) ); + } + + return !m_selection.Empty(); +} + + +bool GERBVIEW_SELECTION_TOOL::selectMultiple() +{ + bool cancelled = false; // Was the tool cancelled while it was running? + m_multiple = true; // Multiple selection mode is active + KIGFX::VIEW* view = getView(); + getViewControls()->SetAutoPan( true ); + + KIGFX::PREVIEW::SELECTION_AREA area; + view->Add( &area ); + + while( OPT_TOOL_EVENT evt = Wait() ) + { + if( evt->IsCancel() ) + { + cancelled = true; + break; + } + + if( evt->IsDrag( BUT_LEFT ) ) + { + + // Start drawing a selection box + area.SetOrigin( evt->DragOrigin() ); + area.SetEnd( evt->Position() ); + area.SetAdditive( m_additive ); + area.SetSubtractive( m_subtractive ); + + view->SetVisible( &area, true ); + view->Update( &area ); + } + + if( evt->IsMouseUp( BUT_LEFT ) ) + { + // End drawing the selection box + view->SetVisible( &area, false ); + + // Mark items within the selection box as selected + std::vector selectedItems; + + // Filter the view items based on the selection box + BOX2I selectionBox = area.ViewBBox(); + view->Query( selectionBox, selectedItems ); // Get the list of selected items + + std::vector::iterator it, it_end; + + int width = area.GetEnd().x - area.GetOrigin().x; + int height = area.GetEnd().y - area.GetOrigin().y; + + // Construct an EDA_RECT to determine EDA_ITEM selection + EDA_RECT selectionRect( wxPoint( area.GetOrigin().x, area.GetOrigin().y ), + wxSize( width, height ) ); + + selectionRect.Normalize(); + + for( it = selectedItems.begin(), it_end = selectedItems.end(); it != it_end; ++it ) + { + auto item = static_cast( it->first ); + + if( !item || !selectable( item ) ) + continue; + + /* Selection mode depends on direction of drag-selection: + * Left > Right : Select objects that are fully enclosed by selection + * Right > Left : Select objects that are crossed by selection + */ + + if( width >= 0 ) + { + if( selectionBox.Contains( item->ViewBBox() ) ) + { + if( m_subtractive ) + unselect( item ); + else + select( item ); + } + } + else + { + if( item->HitTest( selectionRect ) ) + { + if( m_subtractive ) + unselect( item ); + else + select( item ); + } + + } + } + + if( m_selection.Size() == 1 ) + m_frame->SetCurItem( static_cast( m_selection.Front() ) ); + else + m_frame->SetCurItem( NULL ); + + // Inform other potentially interested tools + if( !m_selection.Empty() ) + m_toolMgr->ProcessEvent( SelectedEvent ); + + break; // Stop waiting for events + } + } + + // Stop drawing the selection box + view->Remove( &area ); + m_multiple = false; // Multiple selection mode is inactive + getViewControls()->SetAutoPan( false ); + + return cancelled; +} + + +void GERBVIEW_SELECTION_TOOL::setTransitions() +{ + Go( &GERBVIEW_SELECTION_TOOL::Main, GERBVIEW_ACTIONS::selectionActivate.MakeEvent() ); + Go( &GERBVIEW_SELECTION_TOOL::CursorSelection, GERBVIEW_ACTIONS::selectionCursor.MakeEvent() ); + Go( &GERBVIEW_SELECTION_TOOL::ClearSelection, GERBVIEW_ACTIONS::selectionClear.MakeEvent() ); + Go( &GERBVIEW_SELECTION_TOOL::SelectItem, GERBVIEW_ACTIONS::selectItem.MakeEvent() ); + Go( &GERBVIEW_SELECTION_TOOL::UnselectItem, GERBVIEW_ACTIONS::unselectItem.MakeEvent() ); + Go( &GERBVIEW_SELECTION_TOOL::MeasureTool, GERBVIEW_ACTIONS::measureTool.MakeEvent() ); +} + + +int GERBVIEW_SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent ) +{ + if( m_selection.Empty() ) // Try to find an item that could be modified + { + selectCursor( true ); + + clearSelection(); + return 0; + } + + return 0; +} + + +int GERBVIEW_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent ) +{ + clearSelection(); + + return 0; +} + + +int GERBVIEW_SELECTION_TOOL::SelectItems( const TOOL_EVENT& aEvent ) +{ + std::vector* items = aEvent.Parameter*>(); + + if( items ) + { + // Perform individual selection of each item + // before processing the event. + for( auto item : *items ) + { + select( item ); + } + + m_toolMgr->ProcessEvent( SelectedEvent ); + } + + return 0; +} + + +int GERBVIEW_SELECTION_TOOL::SelectItem( const TOOL_EVENT& aEvent ) +{ + // Check if there is an item to be selected + EDA_ITEM* item = aEvent.Parameter(); + + if( item ) + { + select( item ); + + // Inform other potentially interested tools + m_toolMgr->ProcessEvent( SelectedEvent ); + } + + return 0; +} + + +int GERBVIEW_SELECTION_TOOL::UnselectItems( const TOOL_EVENT& aEvent ) +{ + std::vector* items = aEvent.Parameter*>(); + + if( items ) + { + // Perform individual unselection of each item + // before processing the event + for( auto item : *items ) + { + unselect( item ); + } + + m_toolMgr->ProcessEvent( UnselectedEvent ); + } + + return 0; +} + + +int GERBVIEW_SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent ) +{ + // Check if there is an item to be selected + EDA_ITEM* item = aEvent.Parameter(); + + if( item ) + { + unselect( item ); + + // Inform other potentially interested tools + m_toolMgr->ProcessEvent( UnselectedEvent ); + } + + return 0; +} + + +void GERBVIEW_SELECTION_TOOL::clearSelection() +{ + if( m_selection.Empty() ) + return; + + for( auto item : m_selection ) + unselectVisually( static_cast( item ) ); + + m_selection.Clear(); + + m_frame->SetCurItem( NULL ); + + // Inform other potentially interested tools + m_toolMgr->ProcessEvent( ClearedEvent ); +} + + +void GERBVIEW_SELECTION_TOOL::zoomFitSelection( void ) +{ + //Should recalculate the view to zoom in on the selection + auto selectionBox = m_selection.ViewBBox(); + auto canvas = m_frame->GetGalCanvas(); + auto view = getView(); + + VECTOR2D screenSize = view->ToWorld( canvas->GetClientSize(), false ); + + if( !( selectionBox.GetWidth() == 0 ) || !( selectionBox.GetHeight() == 0 ) ) + { + VECTOR2D vsize = selectionBox.GetSize(); + double scale = view->GetScale() / std::max( fabs( vsize.x / screenSize.x ), + fabs( vsize.y / screenSize.y ) ); + view->SetScale( scale ); + view->SetCenter( selectionBox.Centre() ); + view->Add( &m_selection ); + } + + m_frame->GetGalCanvas()->ForceRefresh(); +} + + +EDA_ITEM* GERBVIEW_SELECTION_TOOL::disambiguationMenu( GERBER_COLLECTOR* aCollector ) +{ + EDA_ITEM* current = NULL; + BRIGHT_BOX brightBox; + CONTEXT_MENU menu; + + getView()->Add( &brightBox ); + + int limit = std::min( 10, aCollector->GetCount() ); + + for( int i = 0; i < limit; ++i ) + { + wxString text; + EDA_ITEM* item = ( *aCollector )[i]; + text = item->GetSelectMenuText(); + menu.Add( text, i + 1 ); + } + + menu.SetTitle( _( "Clarify selection" ) ); + menu.DisplayTitle( true ); + SetContextMenu( &menu, CMENU_NOW ); + + while( OPT_TOOL_EVENT evt = Wait() ) + { + if( evt->Action() == TA_CONTEXT_MENU_UPDATE ) + { + if( current ) + current->ClearBrightened(); + + int id = *evt->GetCommandId(); + + // User has pointed an item, so show it in a different way + if( id > 0 && id <= limit ) + { + current = ( *aCollector )[id - 1]; + current->SetBrightened(); + } + else + { + current = NULL; + } + } + else if( evt->Action() == TA_CONTEXT_MENU_CHOICE ) + { + boost::optional id = evt->GetCommandId(); + + // User has selected an item, so this one will be returned + if( id && ( *id > 0 ) ) + current = ( *aCollector )[*id - 1]; + else + current = NULL; + + break; + } + + // Draw a mark to show which item is available to be selected + if( current && current->IsBrightened() ) + { + brightBox.SetItem( current ); + getView()->SetVisible( &brightBox, true ); +// getView()->Hide( &brightBox, false ); + getView()->Update( &brightBox, KIGFX::GEOMETRY ); + getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY ); + } + } + + getView()->Remove( &brightBox ); + + return current; +} + + +bool GERBVIEW_SELECTION_TOOL::selectable( const EDA_ITEM* aItem ) const +{ + auto item = static_cast( aItem ); + + if( item->GetLayerPolarity() ) + { + // Don't allow selection of invisible negative items + auto rs = static_cast( getView()->GetPainter()->GetSettings() ); + if( !rs->IsShowNegativeItems() ) + return false; + } + + return getEditFrame()->IsLayerVisible( item->GetLayer() ); +} + + +void GERBVIEW_SELECTION_TOOL::select( EDA_ITEM* aItem ) +{ + if( aItem->IsSelected() ) + { + return; + } + + selectVisually( aItem ); + m_selection.Add( aItem ); + getView()->Add( &m_selection ); + + if( m_selection.Size() == 1 ) + { + // Set as the current item, so the information about selection is displayed + m_frame->SetCurItem( static_cast( aItem ), true ); + } + else if( m_selection.Size() == 2 ) // Check only for 2, so it will not be + { // called for every next selected item + // If multiple items are selected, do not show the information about the selected item + m_frame->SetCurItem( NULL, true ); + } +} + + +void GERBVIEW_SELECTION_TOOL::unselect( EDA_ITEM* aItem ) +{ + if( !aItem->IsSelected() ) + return; + + unselectVisually( aItem ); + m_selection.Remove( aItem ); + + if( m_selection.Empty() ) + { + m_frame->SetCurItem( NULL ); + getView()->Remove( &m_selection ); + } +} + + +void GERBVIEW_SELECTION_TOOL::selectVisually( EDA_ITEM* aItem ) const +{ + // Move the item's layer to the front + int layer = static_cast( aItem )->GetLayer(); + m_frame->GetGalCanvas()->SetTopLayer( GERBER_DRAW_LAYER( layer ) ); + + // Hide the original item, so it is shown only on overlay + aItem->SetSelected(); + getView()->Hide( aItem, true ); + getView()->Update( aItem, KIGFX::GEOMETRY ); +} + + +void GERBVIEW_SELECTION_TOOL::unselectVisually( EDA_ITEM* aItem ) const +{ + // Restore original item visibility + aItem->ClearSelected(); + getView()->Hide( aItem, false ); + getView()->Update( aItem, KIGFX::ALL ); +} + + +bool GERBVIEW_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const +{ + const unsigned GRIP_MARGIN = 20; + VECTOR2D margin = getView()->ToWorld( VECTOR2D( GRIP_MARGIN, GRIP_MARGIN ), false ); + + // Check if the point is located within any of the currently selected items bounding boxes + for( auto item : m_selection ) + { + BOX2I itemBox = item->ViewBBox(); + itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item + + if( itemBox.Contains( aPoint ) ) + return true; + } + + return false; +} + + +int GERBVIEW_SELECTION_TOOL::MeasureTool( const TOOL_EVENT& aEvent ) +{ + auto& view = *getView(); + auto& controls = *getViewControls(); + + Activate(); + getEditFrame()->SetToolID( ID_TB_MEASUREMENT_TOOL, + wxCURSOR_PENCIL, _( "Measure distance between two points" ) ); + + KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPtMgr; + KIGFX::PREVIEW::RULER_ITEM ruler( twoPtMgr ); + + view.Add( &ruler ); + view.SetVisible( &ruler, false ); + + bool originSet = false; + + controls.ShowCursor( true ); + controls.SetSnapping( true ); + getViewControls()->SetAdditionalPanButtons( false, 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 ) ) + { + twoPtMgr.SetOrigin( cursorPos ); + twoPtMgr.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 + twoPtMgr.SetOrigin( cursorPos ); + twoPtMgr.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 ) ) ) + { + twoPtMgr.SetAngleSnap( evt->Modifier( MD_CTRL ) ); + twoPtMgr.SetEnd( cursorPos ); + + view.SetVisible( &ruler, true ); + view.Update( &ruler, KIGFX::GEOMETRY ); + } + + else if( evt->IsClick( BUT_RIGHT ) ) + { + m_menu.ShowContextMenu( m_selection ); + } + } + + view.SetVisible( &ruler, false ); + view.Remove( &ruler ); + getViewControls()->SetAdditionalPanButtons( false, false ); + + getEditFrame()->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); + + return 0; +} + + +VECTOR2I SELECTION::GetCenter() const +{ + VECTOR2I centre; + + if( Size() == 1 ) + { + centre = static_cast( Front() )->GetPosition(); + } + else + { + EDA_RECT bbox = Front()->GetBoundingBox(); + auto i = m_items.begin(); + ++i; + + for( ; i != m_items.end(); ++i ) + { + bbox.Merge( (*i)->GetBoundingBox() ); + } + + centre = bbox.Centre(); + } + + return centre; +} + + +const BOX2I SELECTION::ViewBBox() const +{ + EDA_RECT eda_bbox; + + if( Size() == 1 ) + { + eda_bbox = Front()->GetBoundingBox(); + } + else if( Size() > 1 ) + { + eda_bbox = Front()->GetBoundingBox(); + auto i = m_items.begin(); + ++i; + + for( ; i != m_items.end(); ++i ) + { + eda_bbox.Merge( (*i)->GetBoundingBox() ); + } + } + + return BOX2I( eda_bbox.GetOrigin(), eda_bbox.GetSize() ); +} + + +const KIGFX::VIEW_GROUP::ITEMS SELECTION::updateDrawList() const +{ + std::vector items; + + for( auto item : m_items ) + items.push_back( item ); + + return items; +} + + + +const TOOL_EVENT GERBVIEW_SELECTION_TOOL::SelectedEvent( TC_MESSAGE, TA_ACTION, "gerbview.InteractiveSelection.selected" ); +const TOOL_EVENT GERBVIEW_SELECTION_TOOL::UnselectedEvent( TC_MESSAGE, TA_ACTION, "gerbview.InteractiveSelection.unselected" ); +const TOOL_EVENT GERBVIEW_SELECTION_TOOL::ClearedEvent( TC_MESSAGE, TA_ACTION, "gerbview.InteractiveSelection.cleared" ); diff --git a/gerbview/tools/selection_tool.h b/gerbview/tools/selection_tool.h new file mode 100644 index 0000000000..dc0956f96b --- /dev/null +++ b/gerbview/tools/selection_tool.h @@ -0,0 +1,253 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 Jon Evans + * 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 3 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, see . + */ + +#ifndef __GERBVIEW_SELECTION_TOOL_H +#define __GERBVIEW_SELECTION_TOOL_H + +#include +#include + +#include +#include +#include +#include +#include + +#include + +class SELECTION_AREA; +class GERBER_COLLECTOR; + +namespace KIGFX +{ + class GAL; +} + + +/** + * Class GERBVIEW_SELECTION_TOOL + * + * Selection tool for GerbView, based on the one in PcbNew + */ +class GERBVIEW_SELECTION_TOOL : public TOOL_INTERACTIVE +{ +public: + GERBVIEW_SELECTION_TOOL(); + ~GERBVIEW_SELECTION_TOOL(); + + /// @copydoc TOOL_BASE::Init() + bool Init() override; + + /// @copydoc TOOL_BASE::Reset() + void Reset( RESET_REASON aReason ) override; + + /** + * Function Main() + * + * The main loop. + */ + int Main( const TOOL_EVENT& aEvent ); + + /** + * Function GetSelection() + * + * Returns the set of currently selected items. + */ + SELECTION& GetSelection(); + + /** + * Function RequestSelection() + * + * Returns the current selection set, filtered according to aFlags. + * If the set is empty, performs the legacy-style hover selection. + */ + SELECTION& RequestSelection( int aFlags = SELECTION_DEFAULT ); + + inline TOOL_MENU& GetToolMenu() + { + return m_menu; + } + + ///> Select a single item under cursor event handler. + int CursorSelection( const TOOL_EVENT& aEvent ); + + ///> Clear current selection event handler. + int ClearSelection( const TOOL_EVENT& aEvent ); + + ///> Item selection event handler. + int SelectItem( const TOOL_EVENT& aEvent ); + + ///> Multiple item selection event handler + int SelectItems( const TOOL_EVENT& aEvent ); + + ///> Item unselection event handler. + int UnselectItem( const TOOL_EVENT& aEvent ); + + ///> Multiple item unselection event handler + int UnselectItems( const TOOL_EVENT& aEvent ); + + ///> Launches a tool to measure between points + int MeasureTool( const TOOL_EVENT& aEvent ); + + ///> Event sent after an item is selected. + static const TOOL_EVENT SelectedEvent; + + ///> Event sent after an item is unselected. + static const TOOL_EVENT UnselectedEvent; + + ///> Event sent after selection is cleared. + static const TOOL_EVENT ClearedEvent; + + ///> Sets up handlers for various events. + void setTransitions() override; + + ///> Zooms the screen to center and fit the current selection. + void zoomFitSelection( void ); + +private: + /** + * Function selectPoint() + * Selects an item pointed by the parameter aWhere. If there is more than one item at that + * place, there is a menu displayed that allows to choose the item. + * + * @param aWhere is the place where the item should be selected. + * @param aAllowDisambiguation decides what to do in case of disambiguation. If true, then + * a menu is shown, otherise function finishes without selecting anything. + * @return True if an item was selected, false otherwise. + */ + bool selectPoint( const VECTOR2I& aWhere, bool aOnDrag = false ); + + /** + * Function selectCursor() + * Selects an item under the cursor unless there is something already selected or aSelectAlways + * is true. + * @param aSelectAlways forces to select an item even if there is an item already selected. + * @return true if eventually there is an item selected, false otherwise. + */ + bool selectCursor( bool aSelectAlways = false ); + + /** + * Function selectMultiple() + * Handles drawing a selection box that allows to select many items at the same time. + * + * @return true if the function was cancelled (i.e. CancelEvent was received). + */ + bool selectMultiple(); + + /** + * Function clearSelection() + * Clears the current selection. + */ + void clearSelection(); + + /** + * Function disambiguationMenu() + * Handles the menu that allows to select one of many items in case there is more than one + * item at the selected point (@see selectCursor()). + * + * @param aItems contains list of items that are displayed to the user. + */ + EDA_ITEM* disambiguationMenu( GERBER_COLLECTOR* aItems ); + + /** + * Function toggleSelection() + * Changes selection status of a given item. + * + * @param aItem is the item to have selection status changed. + */ + void toggleSelection( EDA_ITEM* aItem ); + + /** + * Function selectable() + * Checks conditions for an item to be selected. + * + * @return True if the item fulfills conditions to be selected. + */ + bool selectable( const EDA_ITEM* aItem ) const; + + /** + * Function select() + * Takes necessary action mark an item as selected. + * + * @param aItem is an item to be selected. + */ + void select( EDA_ITEM* aItem ); + + /** + * Function unselect() + * Takes necessary action mark an item as unselected. + * + * @param aItem is an item to be unselected. + */ + void unselect( EDA_ITEM* aItem ); + + /** + * Function selectVisually() + * Marks item as selected, but does not add it to the ITEMS_PICKED_LIST. + * @param aItem is an item to be be marked. + */ + void selectVisually( EDA_ITEM* aItem ) const; + + /** + * Function unselectVisually() + * Marks item as selected, but does not add it to the ITEMS_PICKED_LIST. + * @param aItem is an item to be be marked. + */ + void unselectVisually( EDA_ITEM* aItem ) const; + + /** + * Function selectionContains() + * Checks if the given point is placed within any of selected items' bounding box. + * + * @return True if the given point is contained in any of selected items' bouding box. + */ + bool selectionContains( const VECTOR2I& aPoint ) const; + + /** + * Function guessSelectionCandidates() + * Tries to guess best selection candidates in case multiple items are clicked, by + * doing some braindead heuristics. + * @param aCollector is the collector that has a list of items to be queried. + */ + void guessSelectionCandidates( GERBER_COLLECTOR& aCollector ) const; + + /// Pointer to the parent frame. + GERBVIEW_FRAME* m_frame; + + /// Current state of selection. + SELECTION m_selection; + + /// Flag saying if items should be added to the current selection or rather replace it. + bool m_additive; + + /// Flag saying if items should be removed from the current selection + bool m_subtractive; + + /// Flag saying if multiple selection mode is active. + bool m_multiple; + + /// Determines if the selection is preliminary or final. + bool m_preliminary; + + /// Menu model displayed by the tool. + TOOL_MENU m_menu; +}; + +#endif