/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 CERN * @author Maciej Suminski * * 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 "drawing_tool.h" #include "common_actions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DRAWING_TOOL::DRAWING_TOOL() : TOOL_INTERACTIVE( "pcbnew.InteractiveDrawing" ) { } DRAWING_TOOL::~DRAWING_TOOL() { } void DRAWING_TOOL::Reset( RESET_REASON aReason ) { // Init variables used by every drawing tool m_view = getView(); m_controls = getViewControls(); m_board = getModel(); m_frame = getEditFrame(); setTransitions(); } int DRAWING_TOOL::DrawLine( TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_PCB_ADD_LINE_BUTT, wxCURSOR_PENCIL, _( "Add graphic line" ) ); return drawSegment( S_SEGMENT, true ); } int DRAWING_TOOL::DrawCircle( TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_PCB_CIRCLE_BUTT, wxCURSOR_PENCIL, _( "Add graphic circle" ) ); return drawSegment( S_CIRCLE, false ); } int DRAWING_TOOL::DrawArc( TOOL_EVENT& aEvent ) { bool clockwise = true; // drawing direction of the arc double startAngle = 0.0f; // angle of the first arc line VECTOR2I cursorPos = m_controls->GetCursorPosition(); DRAWSEGMENT* arc = NULL; DRAWSEGMENT helperLine; helperLine.SetShape( S_SEGMENT ); helperLine.SetLayer( Dwgs_User ); helperLine.SetWidth( 1 ); // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); Activate(); m_frame->SetToolID( ID_PCB_ARC_BUTT, wxCURSOR_PENCIL, _( "Add graphic arc" ) ); enum ARC_STEPS { SET_ORIGIN = 0, SET_END, SET_ANGLE, FINISHED }; int step = SET_ORIGIN; // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) { if( step != SET_ORIGIN ) // start from the beginning { preview.Clear(); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); delete arc; step = SET_ORIGIN; } else break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( evt->IsKeyPressed() && step != SET_ORIGIN ) { int width = arc->GetWidth(); // Modify the new item width if( evt->KeyCode() == '-' && width > WIDTH_STEP ) arc->SetWidth( width - WIDTH_STEP ); else if( evt->KeyCode() == '=' ) arc->SetWidth( width + WIDTH_STEP ); else if( evt->KeyCode() == '/' ) { if( clockwise ) arc->SetAngle( arc->GetAngle() - 3600.0 ); else arc->SetAngle( arc->GetAngle() + 3600.0 ); clockwise = !clockwise; } preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsClick( BUT_LEFT ) ) { switch( step ) { case SET_ORIGIN: { LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer; if( IsCopperLayer( layer ) ) { DisplayInfoMessage( NULL, _( "Graphic not allowed on Copper layers" ) ); --step; } else { // Init the new item attributes arc = new DRAWSEGMENT( m_board ); arc->SetShape( S_ARC ); arc->SetAngle( 0.0 ); arc->SetWidth( m_board->GetDesignSettings().m_DrawSegmentWidth ); arc->SetCenter( wxPoint( cursorPos.x, cursorPos.y ) ); arc->SetLayer( layer ); helperLine.SetStart( arc->GetCenter() ); helperLine.SetEnd( arc->GetCenter() ); preview.Add( arc ); preview.Add( &helperLine ); m_controls->SetAutoPan( true ); } } break; case SET_END: { if( wxPoint( cursorPos.x, cursorPos.y ) != arc->GetCenter() ) { VECTOR2D startLine( arc->GetArcStart() - arc->GetCenter() ); startAngle = startLine.Angle(); arc->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) ); } else --step; // one another chance to draw a proper arc } break; case SET_ANGLE: { if( wxPoint( cursorPos.x, cursorPos.y ) != arc->GetArcStart() ) { assert( arc->GetAngle() != 0 ); assert( arc->GetArcStart() != arc->GetArcEnd() ); assert( arc->GetWidth() > 0 ); m_view->Add( arc ); m_board->Add( arc ); arc->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( arc, UR_NEW ); preview.Remove( arc ); preview.Remove( &helperLine ); } else --step; // one another chance to draw a proper arc } break; } if( ++step == FINISHED ) { step = SET_ORIGIN; m_controls->SetAutoPan( false ); } } else if( evt->IsMotion() ) { switch( step ) { case SET_END: helperLine.SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); arc->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) ); break; case SET_ANGLE: { VECTOR2D endLine( wxPoint( cursorPos.x, cursorPos.y ) - arc->GetCenter() ); double newAngle = RAD2DECIDEG( endLine.Angle() - startAngle ); // Adjust the new angle to (counter)clockwise setting if( clockwise && newAngle < 0.0 ) newAngle += 3600.0; else if( !clockwise && newAngle > 0.0 ) newAngle -= 3600.0; arc->SetAngle( newAngle ); } break; } // Show a preview of the item preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::PlaceTextModule( TOOL_EVENT& aEvent ) { TEXTE_MODULE* text = NULL; // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); m_controls->SetAutoPan( true ); Activate(); m_frame->SetToolID( ID_PCB_ADD_TEXT_BUTT, wxCURSOR_PENCIL, _( "Add text" ) ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { VECTOR2I cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) { if( text ) { // Delete the old text and have another try m_board->Delete( text ); // it was already added by CreateTextPcb() text = NULL; preview.Clear(); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_controls->ShowCursor( true ); } else break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( text && evt->Category() == TC_COMMAND ) { if( evt->IsAction( &COMMON_ACTIONS::rotate ) ) { text->Rotate( text->GetPosition(), 900.0 /*m_frame->GetRotationAngle()*/ ); // FIXME preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsAction( &COMMON_ACTIONS::flip ) ) { text->Flip( text->GetPosition() ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } else if( evt->IsClick( BUT_LEFT ) ) { if( !text ) { // Init the new item attributes text = m_frame->CreateTextModule( m_frame->GetBoard()->m_Modules, NULL ); if( text == NULL ) continue; m_controls->ShowCursor( false ); preview.Add( text ); } else { assert( text->GetText().Length() > 0 ); assert( text->GetSize().x > 0 && text->GetSize().y > 0 ); text->ClearFlags(); m_view->Add( text ); // m_board->Add( text ); // it is already added by CreateTextePcb() text->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( text, UR_NEW ); preview.Remove( text ); m_controls->ShowCursor( true ); text = NULL; } } else if( text && evt->IsMotion() ) { text->SetTextPosition( wxPoint( cursorPos.x, cursorPos.y ) ); // Show a preview of the item preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::PlaceTextPcb( TOOL_EVENT& aEvent ) { TEXTE_PCB* text = NULL; // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); m_controls->SetAutoPan( true ); Activate(); m_frame->SetToolID( ID_PCB_ADD_TEXT_BUTT, wxCURSOR_PENCIL, _( "Add text" ) ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { VECTOR2I cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) { if( text ) { // Delete the old text and have another try m_board->Delete( text ); // it was already added by CreateTextPcb() text = NULL; preview.Clear(); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_controls->ShowCursor( true ); } else break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( text && evt->Category() == TC_COMMAND ) { if( evt->IsAction( &COMMON_ACTIONS::rotate ) ) { text->Rotate( text->GetPosition(), /*m_frame->GetRotationAngle()*/ 900.0 ); // FIXME preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsAction( &COMMON_ACTIONS::flip ) ) { text->Flip( text->GetPosition() ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } else if( evt->IsClick( BUT_LEFT ) ) { if( !text ) { // Init the new item attributes text = static_cast( m_frame )->CreateTextePcb( NULL ); if( text == NULL ) continue; m_controls->ShowCursor( false ); preview.Add( text ); } else { assert( text->GetText().Length() > 0 ); assert( text->GetSize().x > 0 && text->GetSize().y > 0 ); text->ClearFlags(); m_view->Add( text ); // m_board->Add( text ); // it is already added by CreateTextePcb() text->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( text, UR_NEW ); preview.Remove( text ); m_controls->ShowCursor( true ); text = NULL; } } else if( text && evt->IsMotion() ) { text->SetTextPosition( wxPoint( cursorPos.x, cursorPos.y ) ); // Show a preview of the item preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::DrawDimension( TOOL_EVENT& aEvent ) { DIMENSION* dimension = NULL; int width, maxThickness; // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); Activate(); m_frame->SetToolID( ID_PCB_DIMENSION_BUTT, wxCURSOR_PENCIL, _( "Add dimension" ) ); enum DIMENSION_STEPS { SET_ORIGIN = 0, SET_END, SET_HEIGHT, FINISHED }; int step = SET_ORIGIN; // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { VECTOR2I cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) { if( step != SET_ORIGIN ) // start from the beginning { preview.Clear(); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); delete dimension; step = SET_ORIGIN; } else break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( evt->IsKeyPressed() && step != SET_ORIGIN ) { width = dimension->GetWidth(); // Modify the new item width if( evt->KeyCode() == '-' && width > WIDTH_STEP ) dimension->SetWidth( width - WIDTH_STEP ); else if( evt->KeyCode() == '=' ) dimension->SetWidth( width + WIDTH_STEP ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsClick( BUT_LEFT ) ) { switch( step ) { case SET_ORIGIN: { LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer; if( IsCopperLayer( layer ) || layer == Edge_Cuts ) { DisplayInfoMessage( NULL, _( "Dimension not allowed on Copper or Edge Cut layers" ) ); --step; } else { // Init the new item attributes dimension = new DIMENSION( m_board ); dimension->SetLayer( layer ); dimension->SetOrigin( wxPoint( cursorPos.x, cursorPos.y ) ); dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); dimension->Text().SetSize( m_board->GetDesignSettings().m_PcbTextSize ); width = m_board->GetDesignSettings().m_PcbTextWidth; maxThickness = Clamp_Text_PenSize( width, dimension->Text().GetSize() ); if( width > maxThickness ) width = maxThickness; dimension->Text().SetThickness( width ); dimension->SetWidth( width ); dimension->AdjustDimensionDetails(); preview.Add( dimension ); m_controls->SetAutoPan( true ); } } break; case SET_END: dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); // Dimensions that have origin and end in the same spot are not valid if( dimension->GetOrigin() == dimension->GetEnd() ) --step; break; case SET_HEIGHT: { if( wxPoint( cursorPos.x, cursorPos.y ) != dimension->GetPosition() ) { assert( dimension->GetOrigin() != dimension->GetEnd() ); assert( dimension->GetWidth() > 0 ); m_view->Add( dimension ); m_board->Add( dimension ); dimension->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( dimension, UR_NEW ); preview.Remove( dimension ); } } break; } if( ++step == FINISHED ) { step = SET_ORIGIN; m_controls->SetAutoPan( false ); } } else if( evt->IsMotion() ) { switch( step ) { case SET_END: dimension->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); break; case SET_HEIGHT: { // Calculating the direction of travel perpendicular to the selected axis double angle = dimension->GetAngle() + ( M_PI / 2 ); wxPoint pos( cursorPos.x, cursorPos.y ); wxPoint delta( pos - dimension->m_featureLineDO ); double height = ( delta.x * cos( angle ) ) + ( delta.y * sin( angle ) ); dimension->SetHeight( height ); } break; } // Show a preview of the item preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } if( step != SET_ORIGIN ) delete dimension; m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::DrawZone( TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_PCB_ZONES_BUTT, wxCURSOR_PENCIL, _( "Add zones" ) ); return drawZone( false ); } int DRAWING_TOOL::DrawKeepout( TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_PCB_KEEPOUT_AREA_BUTT, wxCURSOR_PENCIL, _( "Add keepout" ) ); return drawZone( true ); } int DRAWING_TOOL::PlaceTarget( TOOL_EVENT& aEvent ) { PCB_TARGET* target = new PCB_TARGET( m_board ); // Init the new item attributes target->SetLayer( Edge_Cuts ); target->SetWidth( m_board->GetDesignSettings().m_EdgeSegmentWidth ); target->SetSize( Millimeter2iu( 5 ) ); VECTOR2I cursorPos = m_controls->GetCursorPosition(); target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) ); // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); preview.Add( target ); m_view->Add( &preview ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->SetSnapping( true ); m_controls->SetAutoPan( true ); Activate(); m_frame->SetToolID( ID_PCB_MIRE_BUTT, wxCURSOR_PENCIL, _( "Add layer alignment target" ) ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) break; else if( evt->IsKeyPressed() ) { int width = target->GetWidth(); // Modify the new item width if( evt->KeyCode() == '-' && width > WIDTH_STEP ) target->SetWidth( width - WIDTH_STEP ); else if( evt->KeyCode() == '=' ) target->SetWidth( width + WIDTH_STEP ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsClick( BUT_LEFT ) ) { assert( target->GetSize() > 0 ); assert( target->GetWidth() > 0 ); m_view->Add( target ); m_board->Add( target ); target->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( target, UR_NEW ); preview.Remove( target ); // Create next PCB_TARGET target = new PCB_TARGET( *target ); preview.Add( target ); } else if( evt->IsMotion() ) { target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } delete target; m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::PlaceModule( TOOL_EVENT& aEvent ) { MODULE* module = NULL; // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); m_controls->SetAutoPan( true ); Activate(); m_frame->SetToolID( ID_PCB_MODULE_BUTT, wxCURSOR_HAND, _( "Add module" ) ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { VECTOR2I cursorPos = m_controls->GetCursorPosition(); if( evt->IsCancel() || evt->IsActivate() ) { if( module ) { m_board->Delete( module ); // it was added by LoadModuleFromLibrary() module = NULL; preview.Clear(); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_controls->ShowCursor( true ); } else break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( module && evt->Category() == TC_COMMAND ) { if( evt->IsAction( &COMMON_ACTIONS::rotate ) ) { module->Rotate( module->GetPosition(), /*m_frame->GetRotationAngle()*/ 900.0 ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else if( evt->IsAction( &COMMON_ACTIONS::flip ) ) { module->Flip( module->GetPosition() ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } else if( evt->IsClick( BUT_LEFT ) ) { if( !module ) { // Init the new item attributes module = m_frame->LoadModuleFromLibrary( wxEmptyString, m_frame->Prj().PcbFootprintLibs(), true, NULL ); if( module == NULL ) continue; m_controls->ShowCursor( false ); module->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) ); // Add all the drawable parts to preview preview.Add( module ); module->RunOnChildren( boost::bind( &KIGFX::VIEW_GROUP::Add, &preview, _1 ) ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } else { module->RunOnChildren( boost::bind( &KIGFX::VIEW::Add, m_view, _1 ) ); m_view->Add( module ); module->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( module, UR_NEW ); // Remove from preview preview.Remove( module ); module->RunOnChildren( boost::bind( &KIGFX::VIEW_GROUP::Remove, &preview, _1 ) ); module = NULL; // to indicate that there is no module that we currently modify m_controls->ShowCursor( true ); } } else if( module && evt->IsMotion() ) { module->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) ); preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::drawSegment( int aShape, bool aContinous ) { // Only two shapes are currently supported assert( aShape == S_SEGMENT || aShape == S_CIRCLE ); DRAWSEGMENT* graphic = NULL; DRAWSEGMENT line45; // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); Activate(); bool direction45 = false; // 45 degrees only mode int addedSegments = 0; // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { bool updatePreview = false; // should preview be updated VECTOR2I cursorPos = m_controls->GetCursorPosition(); // Enable 45 degrees lines only mode by holding control if( direction45 != evt->Modifier( MD_CTRL ) && graphic && aShape == S_SEGMENT ) { direction45 = evt->Modifier( MD_CTRL ); if( direction45 ) { preview.Add( &line45 ); make45DegLine( graphic, &line45 ); } else { preview.Remove( &line45 ); graphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); } updatePreview = true; } if( evt->IsCancel() || evt->IsActivate() ) { if( !graphic ) break; preview.Clear(); updatePreview = true; delete graphic; graphic = NULL; m_controls->SetAutoPan( false ); if( evt->IsActivate() ) // now finish unconditionally break; } else if( graphic && evt->IsKeyPressed() ) { int width = graphic->GetWidth(); // Modify the new item width if( evt->KeyCode() == '-' && width > WIDTH_STEP ) graphic->SetWidth( width - WIDTH_STEP ); else if( evt->KeyCode() == '=' ) graphic->SetWidth( width + WIDTH_STEP ); updatePreview = true; } else if( evt->IsClick( BUT_LEFT ) ) { if( !graphic ) { LAYER_ID layer = m_frame->GetScreen()->m_Active_Layer; if( IsCopperLayer( layer ) ) { DisplayInfoMessage( NULL, _( "Graphic not allowed on Copper layers" ) ); } else { // Init the new item attributes graphic = new DRAWSEGMENT( m_board ); graphic->SetShape( (STROKE_T) aShape ); graphic->SetWidth( m_board->GetDesignSettings().m_DrawSegmentWidth ); graphic->SetStart( wxPoint( cursorPos.x, cursorPos.y ) ); graphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); graphic->SetLayer( layer ); if( aShape == S_SEGMENT ) { line45 = *graphic; // used only for direction 45 mode with lines line45.SetLayer( layer ); } preview.Add( graphic ); m_controls->SetAutoPan( true ); } } else { if( graphic->GetEnd() != graphic->GetStart() ) { assert( graphic->GetLength() > 0 ); assert( graphic->GetWidth() > 0 ); m_view->Add( graphic ); m_board->Add( graphic ); graphic->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( graphic, UR_NEW ); preview.Remove( graphic ); ++addedSegments; if( aContinous ) { graphic = new DRAWSEGMENT( *graphic ); // Start the new line in the same spot where the previous one has ended graphic->SetStart( graphic->GetEnd() ); if( direction45 ) graphic->SetEnd( line45.GetEnd() ); preview.Add( graphic ); } else // start a new graphic { addedSegments = 0; m_controls->SetAutoPan( false ); graphic = NULL; } } else if( addedSegments > 0 ) // User has clicked twice in the same spot { // a clear sign that the current drawing is finished preview.Clear(); // but only if at least one graphic was created // otherwise - force user to draw more or cancel delete graphic; graphic = NULL; m_controls->SetAutoPan( false ); } } } else if( graphic && evt->IsMotion() ) { // 45 degree lines if( direction45 && aShape == S_SEGMENT ) make45DegLine( graphic, &line45 ); else graphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); updatePreview = true; } if( updatePreview ) preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } int DRAWING_TOOL::drawZone( bool aKeepout ) { ZONE_CONTAINER* zone = NULL; DRAWSEGMENT line45; DRAWSEGMENT* helperLine = NULL; // we will need more than one helper line // Add a VIEW_GROUP that serves as a preview for the new item KIGFX::VIEW_GROUP preview( m_view ); m_view->Add( &preview ); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear ); m_controls->ShowCursor( true ); m_controls->SetSnapping( true ); Activate(); VECTOR2I lastCursorPos = m_controls->GetCursorPosition(); VECTOR2I origin; int numPoints = 0; bool direction45 = false; // 45 degrees only mode // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { bool updatePreview = false; // should preview be updated VECTOR2I cursorPos = m_controls->GetCursorPosition(); // Enable 45 degrees lines only mode by holding control if( direction45 != ( evt->Modifier( MD_CTRL ) && numPoints > 0 ) ) { direction45 = evt->Modifier( MD_CTRL ); if( direction45 ) { preview.Add( &line45 ); make45DegLine( helperLine, &line45 ); } else { preview.Remove( &line45 ); helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); } updatePreview = true; } if( evt->IsCancel() || evt->IsActivate() ) { if( numPoints > 0 ) // cancel the current zone { delete zone; zone = NULL; m_controls->SetAutoPan( false ); if( direction45 ) { preview.Remove( &line45 ); direction45 = false; } preview.FreeItems(); updatePreview = true; numPoints = 0; } else // there is no zone currently drawn - just stop the tool break; if( evt->IsActivate() ) // now finish unconditionally break; } else if( evt->IsClick( BUT_LEFT ) ) { // Check if it is double click / closing line (so we have to finish the zone) if( lastCursorPos == cursorPos || ( numPoints > 0 && cursorPos == origin ) ) { if( numPoints > 2 ) // valid zone consists of more than 2 points { assert( zone->GetNumCorners() > 2 ); // Finish the zone zone->Outline()->CloseLastContour(); zone->Outline()->RemoveNullSegments(); m_board->Add( zone ); m_view->Add( zone ); if( !aKeepout ) static_cast( m_frame )->Fill_Zone( zone ); zone->ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); m_frame->OnModify(); m_frame->SaveCopyInUndoList( zone, UR_NEW ); zone = NULL; } else { delete zone; zone = NULL; } numPoints = 0; m_controls->SetAutoPan( false ); if( direction45 ) { preview.Remove( &line45 ); direction45 = false; } preview.FreeItems(); updatePreview = true; } else { if( numPoints == 0 ) // it's the first click { // Get the current default settings for zones ZONE_SETTINGS zoneInfo = m_frame->GetZoneSettings(); zoneInfo.m_CurrentZone_Layer = m_frame->GetScreen()->m_Active_Layer; m_controls->SetAutoPan( true ); // Show options dialog ZONE_EDIT_T dialogResult; if( aKeepout ) dialogResult = InvokeKeepoutAreaEditor( m_frame, &zoneInfo ); else { if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) ) dialogResult = InvokeCopperZonesEditor( m_frame, &zoneInfo ); else dialogResult = InvokeNonCopperZonesEditor( m_frame, NULL, &zoneInfo ); } if( dialogResult == ZONE_ABORT ) { m_controls->SetAutoPan( false ); continue; } // Apply the selected settings zone = new ZONE_CONTAINER( m_board ); zoneInfo.ExportSetting( *zone ); m_frame->GetGalCanvas()->SetTopLayer( zoneInfo.m_CurrentZone_Layer ); // Add the first point zone->Outline()->Start( zoneInfo.m_CurrentZone_Layer, cursorPos.x, cursorPos.y, zone->GetHatchStyle() ); origin = cursorPos; // Helper line represents the currently drawn line of the zone polygon helperLine = new DRAWSEGMENT; helperLine->SetShape( S_SEGMENT ); helperLine->SetWidth( 1 ); helperLine->SetLayer( zoneInfo.m_CurrentZone_Layer ); helperLine->SetStart( wxPoint( cursorPos.x, cursorPos.y ) ); helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); line45 = *helperLine; preview.Add( helperLine ); } else { zone->AppendCorner( helperLine->GetEnd() ); helperLine = new DRAWSEGMENT( *helperLine ); helperLine->SetStart( helperLine->GetEnd() ); preview.Add( helperLine ); } ++numPoints; updatePreview = true; } lastCursorPos = cursorPos; } else if( evt->IsMotion() && numPoints > 0 ) { // 45 degree lines if( direction45 ) make45DegLine( helperLine, &line45 ); else helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); // Show a preview of the item updatePreview = true; } if( updatePreview ) preview.ViewUpdate( KIGFX::VIEW_ITEM::GEOMETRY ); } m_controls->ShowCursor( false ); m_controls->SetSnapping( false ); m_controls->SetAutoPan( false ); m_view->Remove( &preview ); setTransitions(); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); return 0; } void DRAWING_TOOL::make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper ) const { VECTOR2I cursorPos = m_controls->GetCursorPosition(); VECTOR2I origin( aSegment->GetStart() ); DIRECTION_45 direction( origin - cursorPos ); SHAPE_LINE_CHAIN newChain = direction.BuildInitialTrace( origin, cursorPos ); if( newChain.PointCount() > 2 ) { aSegment->SetEnd( wxPoint( newChain.Point( -2 ).x, newChain.Point( -2 ).y ) ); aHelper->SetStart( wxPoint( newChain.Point( -2 ).x, newChain.Point( -2 ).y ) ); aHelper->SetEnd( wxPoint( newChain.Point( -1 ).x, newChain.Point( -1 ).y ) ); } else { aSegment->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); aHelper->SetStart( wxPoint( cursorPos.x, cursorPos.y ) ); aHelper->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) ); } } void DRAWING_TOOL::setTransitions() { Go( &DRAWING_TOOL::DrawLine, COMMON_ACTIONS::drawLine.MakeEvent() ); Go( &DRAWING_TOOL::DrawCircle, COMMON_ACTIONS::drawCircle.MakeEvent() ); Go( &DRAWING_TOOL::DrawArc, COMMON_ACTIONS::drawArc.MakeEvent() ); Go( &DRAWING_TOOL::DrawDimension, COMMON_ACTIONS::drawDimension.MakeEvent() ); Go( &DRAWING_TOOL::DrawZone, COMMON_ACTIONS::drawZone.MakeEvent() ); Go( &DRAWING_TOOL::DrawKeepout, COMMON_ACTIONS::drawKeepout.MakeEvent() ); Go( &DRAWING_TOOL::PlaceTextPcb, COMMON_ACTIONS::placeTextPcb.MakeEvent() ); Go( &DRAWING_TOOL::PlaceTextModule, COMMON_ACTIONS::placeTextModule.MakeEvent() ); Go( &DRAWING_TOOL::PlaceTarget, COMMON_ACTIONS::placeTarget.MakeEvent() ); Go( &DRAWING_TOOL::PlaceModule, COMMON_ACTIONS::placeModule.MakeEvent() ); }