Files
Seth Hillbrand 592150ae53 ADDED: Polygon vertex editor
Adds a modeless dialog that shows the currently selected polygon or
zone's outline points and allows exact editing of the points

Fixes https://gitlab.com/kicad/code/kicad/issues/21873
2025-10-04 07:02:33 -07:00

369 lines
9.5 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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 <http://www.gnu.org/licenses/>.
*/
#include "vertex_editor_pane.h"
#include <limits>
#include <wx/aui/aui.h>
#include <wx/grid.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/translation.h>
#include <board_item.h>
#include <class_draw_panel_gal.h>
#include <eda_units.h>
#include <pcb_base_edit_frame.h>
#include <pcb_shape.h>
#include <tool/edit_points.h>
#include <tool/point_editor_behavior.h>
#include <tool/tool_manager.h>
#include <tool/actions.h>
#include <zone.h>
#include <view/view.h>
PCB_VERTEX_EDITOR_PANE::PCB_VERTEX_EDITOR_PANE( PCB_BASE_EDIT_FRAME* aFrame ) :
wxPanel( aFrame ),
m_frame( aFrame ),
m_item( nullptr ),
m_zone( nullptr ),
m_shape( nullptr ),
m_grid( nullptr ),
m_updatingGrid( false )
{
wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
m_grid = new wxGrid( this, wxID_ANY );
m_grid->CreateGrid( 0, 2 );
m_grid->SetRowLabelSize( 0 );
m_grid->SetColLabelValue( 0, _( "X coord" ) );
m_grid->SetColLabelValue( 1, _( "Y coord" ) );
m_grid->EnableDragGridSize( false );
m_grid->EnableDragRowSize( false );
m_grid->EnableDragColSize( false );
m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
m_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
m_grid->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_ALWAYS );
m_grid->EnableScrolling( false, true );
m_grid->SetColMinimalWidth( 0, FromDIP( 120 ) );
m_grid->SetColMinimalWidth( 1, FromDIP( 120 ) );
topSizer->Add( m_grid, 1, wxEXPAND | wxALL, FromDIP( 5 ) );
SetSizer( topSizer );
Bind( wxEVT_SIZE, &PCB_VERTEX_EDITOR_PANE::OnSize, this );
m_grid->Bind( wxEVT_GRID_CELL_CHANGED, &PCB_VERTEX_EDITOR_PANE::OnGridCellChange, this );
m_grid->Bind( wxEVT_GRID_SELECT_CELL, &PCB_VERTEX_EDITOR_PANE::OnGridSelectCell, this );
// Initial column sizing
resizeColumns();
}
PCB_VERTEX_EDITOR_PANE::~PCB_VERTEX_EDITOR_PANE()
{
if( m_frame )
m_frame->OnVertexEditorPaneClosed( this );
}
void PCB_VERTEX_EDITOR_PANE::SetItem( BOARD_ITEM* aItem )
{
m_item = aItem;
m_zone = dynamic_cast<ZONE*>( aItem );
m_shape = dynamic_cast<PCB_SHAPE*>( aItem );
refreshGrid();
if( m_grid->GetNumberRows() > 0 )
{
m_grid->SetGridCursor( 0, 0 );
m_grid->SelectRow( 0 );
updateHighlight( 0 );
}
resizeColumns();
}
void PCB_VERTEX_EDITOR_PANE::ClearItem()
{
m_item = nullptr;
m_zone = nullptr;
m_shape = nullptr;
m_updatingGrid = true;
if( m_grid->GetNumberRows() > 0 )
m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
m_rows.clear();
m_updatingGrid = false;
}
void PCB_VERTEX_EDITOR_PANE::OnSelectionChanged( BOARD_ITEM* aNewItem )
{
// Check if the new item is a polygon or zone
if( aNewItem )
{
ZONE* zone = dynamic_cast<ZONE*>( aNewItem );
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aNewItem );
bool isPolygon = ( zone != nullptr ) || ( shape && shape->GetShape() == SHAPE_T::POLY );
if( isPolygon )
{
// Update to the new item
SetItem( aNewItem );
}
else
{
// Not a polygon/zone, clear the editor
ClearItem();
}
}
else
{
// Nothing selected, clear the editor
ClearItem();
}
}
void PCB_VERTEX_EDITOR_PANE::refreshGrid()
{
m_updatingGrid = true;
if( m_grid->GetNumberRows() > 0 )
m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
m_rows.clear();
SHAPE_POLY_SET* poly = getPoly();
if( poly )
{
for( auto it = poly->CIterateWithHoles(); it; ++it )
{
m_rows.push_back( it.GetIndex() );
m_grid->AppendRows( 1 );
updateRow( m_grid->GetNumberRows() - 1 );
}
}
m_updatingGrid = false;
}
void PCB_VERTEX_EDITOR_PANE::updateRow( int aRow )
{
if( aRow < 0 || aRow >= m_grid->GetNumberRows() )
return;
SHAPE_POLY_SET* poly = getPoly();
if( !poly || aRow >= (int) m_rows.size() )
return;
const SHAPE_POLY_SET::VERTEX_INDEX& idx = m_rows[aRow];
VECTOR2I vertex = poly->CVertex( idx );
m_updatingGrid = true;
m_grid->SetCellValue( aRow, 0, formatCoord( vertex.x, ORIGIN_TRANSFORMS::ABS_X_COORD ) );
m_grid->SetCellValue( aRow, 1, formatCoord( vertex.y, ORIGIN_TRANSFORMS::ABS_Y_COORD ) );
m_updatingGrid = false;
}
void PCB_VERTEX_EDITOR_PANE::updateHighlight( int aRow )
{
// Highlighting is now handled by the point editor tool
// We just refresh the view
if( m_frame && m_frame->GetCanvas() )
{
m_frame->GetCanvas()->GetView()->Update( m_item );
m_frame->GetCanvas()->Refresh();
}
}
void PCB_VERTEX_EDITOR_PANE::onPolygonModified()
{
if( m_zone )
{
m_zone->UnFill();
m_zone->SetNeedRefill( true );
m_zone->HatchBorder();
}
if( m_frame && m_frame->GetCanvas() )
{
m_frame->GetCanvas()->GetView()->Update( m_item );
m_frame->GetCanvas()->Refresh();
}
}
bool PCB_VERTEX_EDITOR_PANE::parseCellValue( const wxString& aText,
ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType,
int& aResult ) const
{
long long displayValue = EDA_UNIT_UTILS::UI::ValueFromString( pcbIUScale, m_frame->GetUserUnits(), aText );
if( !displayValue )
return false;
long long internal = m_frame->GetOriginTransforms().FromDisplay( displayValue, aCoordType );
if( internal < std::numeric_limits<int>::min() || internal > std::numeric_limits<int>::max() )
return false;
aResult = static_cast<int>( internal );
return true;
}
wxString PCB_VERTEX_EDITOR_PANE::formatCoord( int aValue,
ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType ) const
{
int displayValue = m_frame->GetOriginTransforms().ToDisplay( aValue, aCoordType );
return m_frame->MessageTextFromValue( displayValue );
}
SHAPE_POLY_SET* PCB_VERTEX_EDITOR_PANE::getPoly()
{
if( m_zone )
return m_zone->Outline();
if( m_shape && m_shape->GetShape() == SHAPE_T::POLY )
return &m_shape->GetPolyShape();
return nullptr;
}
const SHAPE_POLY_SET* PCB_VERTEX_EDITOR_PANE::getPoly() const
{
if( m_zone )
return m_zone->Outline();
if( m_shape && m_shape->GetShape() == SHAPE_T::POLY )
return &m_shape->GetPolyShape();
return nullptr;
}
void PCB_VERTEX_EDITOR_PANE::OnGridCellChange( wxGridEvent& aEvent )
{
if( m_updatingGrid )
return;
int row = aEvent.GetRow();
int col = aEvent.GetCol();
if( row < 0 || row >= (int) m_rows.size() )
{
updateHighlight( -1 );
return;
}
SHAPE_POLY_SET* poly = getPoly();
if( !poly || !m_item )
return;
const SHAPE_POLY_SET::VERTEX_INDEX& idx = m_rows[row];
VECTOR2I vertex = poly->CVertex( idx );
int newValue;
ORIGIN_TRANSFORMS::COORD_TYPES_T coordType =
( col == 0 ) ? ORIGIN_TRANSFORMS::ABS_X_COORD : ORIGIN_TRANSFORMS::ABS_Y_COORD;
if( !parseCellValue( m_grid->GetCellValue( row, col ), coordType, newValue ) )
{
updateRow( row );
return;
}
int& target = ( col == 0 ) ? vertex.x : vertex.y;
if( target == newValue )
{
updateRow( row );
return;
}
// Create a new commit for each edit
BOARD_COMMIT commit( m_frame );
commit.Modify( m_item );
target = newValue;
poly->SetVertex( idx, vertex );
if( m_zone )
{
m_zone->UnFill();
m_zone->SetNeedRefill( true );
m_zone->HatchBorder();
}
commit.Push( _( "Edit Vertex" ) );
// Update the view after the commit
if( m_frame && m_frame->GetCanvas() )
{
m_frame->GetCanvas()->GetView()->Update( m_item );
m_frame->GetCanvas()->Refresh();
}
updateRow( row );
updateHighlight( row );
}
void PCB_VERTEX_EDITOR_PANE::OnGridSelectCell( wxGridEvent& aEvent )
{
updateHighlight( aEvent.GetRow() );
aEvent.Skip();
}
void PCB_VERTEX_EDITOR_PANE::resizeColumns()
{
if( !m_grid || m_grid->GetNumberCols() != 2 )
return;
// Get the available width (excluding row labels and scrollbar)
int width = m_grid->GetClientSize().GetWidth() - m_grid->GetRowLabelSize();
// Reserve space for vertical scrollbar if it's shown
if( m_grid->GetScrollRange( wxVERTICAL ) > 0 )
width -= wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
// Split the width equally between the two columns
int colWidth = width / 2;
if( colWidth > m_grid->GetColMinimalAcceptableWidth() )
{
m_grid->SetColSize( 0, colWidth );
m_grid->SetColSize( 1, colWidth );
}
}
void PCB_VERTEX_EDITOR_PANE::OnSize( wxSizeEvent& aEvent )
{
resizeColumns();
aEvent.Skip();
}