KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcbnew/dialogs/dialog_shape_properties.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019 Jean-Pierre Charras jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Edit properties of Lines, Circles, Arcs and Polygons for PCBNew and Footprint Editor
23 */
25
26#include <wx/valnum.h>
27
28#include <pcb_base_edit_frame.h>
29#include <pcb_edit_frame.h>
30#include <board_commit.h>
35#include <string_utils.h>
36#include <tool/tool_manager.h>
37#include <tool/actions.h>
38#include <pcb_shape.h>
39#include <macros.h>
40#include <algorithm>
41#include <cstdlib>
42#include <widgets/unit_binder.h>
43
45#include <tools/drawing_tool.h>
46#include <line_ending.h>
48
49
50// Mapping between wxChoice index and LINE_ENDING_STYLE enum.
51// Dropdown order: None, Arrow, Open Arrow, Circle, Square
52static bool isOpenShape( SHAPE_T aShape )
53{
54 return aShape == SHAPE_T::ARC || aShape == SHAPE_T::BEZIER || aShape == SHAPE_T::SEGMENT;
55}
56
57
67class GEOM_SYNCER : public wxEvtHandler
68{
69public:
70 GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
71 m_shape( aShape ),
72 m_boundCtrls( aBoundCtrls )
73 {
74 }
75
76 void BindCtrls( size_t aFrom, size_t aTo, std::function<void()> aCb )
77 {
78 wxCHECK( aFrom < m_boundCtrls.size(), /* void */ );
79 wxCHECK( aTo < m_boundCtrls.size(), /* void */ );
80
81 for( size_t i = aFrom; i <= aTo; ++i )
82 {
83 m_boundCtrls[i].m_Ctrl->Bind( wxEVT_TEXT,
84 [aCb]( wxCommandEvent& aEvent )
85 {
86 aCb();
87 } );
88 }
89 }
90
91 void SetShape( PCB_SHAPE& aShape )
92 {
93 m_shape = aShape;
94 updateAll();
95 }
96
97 virtual bool Validate( wxArrayString& aErrs ) const { return true; }
98
99protected:
100 virtual void updateAll() = 0;
101
102 wxTextCtrl* GetCtrl( size_t aIndex ) const
103 {
104 wxCHECK( aIndex < m_boundCtrls.size(), nullptr );
105 return m_boundCtrls[aIndex].m_Ctrl;
106 }
107
108 int GetIntValue( size_t aIndex ) const
109 {
110 wxCHECK( aIndex < m_boundCtrls.size(), 0.0 );
111 return static_cast<int>( m_boundCtrls[aIndex].m_Binder->GetValue() );
112 }
113
114 EDA_ANGLE GetAngleValue( size_t aIndex ) const
115 {
116 wxCHECK( aIndex < m_boundCtrls.size(), EDA_ANGLE() );
117 return m_boundCtrls[aIndex].m_Binder->GetAngleValue();
118 }
119
120 void ChangeValue( size_t aIndex, int aValue )
121 {
122 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
123 m_boundCtrls[aIndex].m_Binder->ChangeValue( aValue );
124 }
125
126 void ChangeAngleValue( size_t aIndex, const EDA_ANGLE& aValue )
127 {
128 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
129 m_boundCtrls[aIndex].m_Binder->ChangeAngleValue( aValue );
130 }
131
133
134 const PCB_SHAPE& GetShape() const { return m_shape; }
135
136private:
138 std::vector<BOUND_CONTROL>& m_boundCtrls;
139};
140
141
146{
147public:
165
166 RECTANGLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
167 GEOM_SYNCER( aShape, aBoundCtrls )
168 {
169 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
170 wxASSERT( GetShape().GetShape() == SHAPE_T::RECTANGLE );
171
173 [this]()
174 {
176 } );
177
179 [this]()
180 {
182 } );
183
185 [this]()
186 {
188 } );
189 }
190
191 bool Validate( wxArrayString& aErrs ) const override
192 {
194 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
195
196 if( p0 == p1 )
197 {
198 aErrs.push_back( _( "Rectangle cannot be zero-sized." ) );
199 return false;
200 }
201
202 return true;
203 }
204
206
208
209 void updateAll() override
210 {
214 }
215
217 {
219 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
220
221 GetShape().SetStart( p0 );
222 GetShape().SetEnd( p1 );
223
226 }
227
229 {
230 const VECTOR2I p0 = GetShape().GetStart();
231 const VECTOR2I p1 = GetShape().GetEnd();
232
233 ChangeValue( START_X, p0.x );
234 ChangeValue( START_Y, p0.y );
235 ChangeValue( END_X, p1.x );
236 ChangeValue( END_Y, p1.y );
237 }
238
240 {
242 const VECTOR2I size{ GetIntValue( CORNER_W ), GetIntValue( CORNER_H ) };
243
244 GetShape().SetStart( p0 );
245 GetShape().SetEnd( p0 + size );
246
249 }
250
260
262 {
264 const VECTOR2I size = { GetIntValue( CENTER_W ), GetIntValue( CENTER_H ) };
265
266 GetShape().SetStart( center - size / 2 );
267 GetShape().SetEnd( center + size / 2 );
268
271 }
272
282};
283
284
286{
287public:
307
308 LINE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
309 GEOM_SYNCER( aShape, aBoundCtrls )
310 {
311 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
312 wxASSERT( GetShape().GetShape() == SHAPE_T::SEGMENT );
313
315 [this]()
316 {
317 OnEndsChange();
318 } );
319
321 [this]()
322 {
324 } );
325
327 [this]()
328 {
330 } );
331 }
332
333 void updateAll() override
334 {
335 updateEnds();
336 updatePolar();
338 }
339
341 {
343 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
344
345 GetShape().SetStart( p0 );
346 GetShape().SetEnd( p1 );
347
348 updatePolar();
350 }
351
353 {
354 const VECTOR2I p0 = GetShape().GetStart();
355 const VECTOR2I p1 = GetShape().GetEnd();
356
357 ChangeValue( START_X, p0.x );
358 ChangeValue( START_Y, p0.y );
359 ChangeValue( END_X, p1.x );
360 ChangeValue( END_Y, p1.y );
361 }
362
364 {
366 const int length = GetIntValue( LENGTH );
367 const EDA_ANGLE angle = GetAngleValue( ANGLE );
368
369 const VECTOR2I polar = GetRotated( VECTOR2I{ length, 0 }, angle );
370
371 GetShape().SetStart( p0 );
372 GetShape().SetEnd( p0 + polar );
373
374 updateEnds();
376 }
377
379 {
380 const VECTOR2I p0 = GetShape().GetStart();
381 const VECTOR2I p1 = GetShape().GetEnd();
382
385 ChangeValue( LENGTH, KiROUND( p0.Distance( p1 ) ) );
386 ChangeAngleValue( ANGLE, -EDA_ANGLE( p1 - p0 ) );
387 }
388
390 {
392 const VECTOR2I mid{ GetIntValue( MID_X ), GetIntValue( MID_Y ) };
393
394 GetShape().SetStart( start );
395 GetShape().SetEnd( mid - ( start - mid ) );
396
397 updateEnds();
398 updatePolar();
399 }
400
402 {
403 const VECTOR2I s = GetShape().GetStart();
404 const VECTOR2I c = GetShape().GetCenter();
405
406 ChangeValue( MID_X, c.x );
407 ChangeValue( MID_Y, c.y );
410 }
411};
412
413
415{
416public:
418 {
419 // CSA: center, start, angle
425
426 // SME: start, mid, end
433
434 // CRAA: center, radius, angle (start), angle (end)
440
442 };
443
444 ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
445 GEOM_SYNCER( aShape, aBoundCtrls )
446 {
447 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
448 wxASSERT( GetShape().GetShape() == SHAPE_T::ARC );
449
451 [this]()
452 {
453 OnCSAChange();
454 } );
455
457 [this]()
458 {
459 OnSMEChange();
460 } );
461
463 [this]()
464 {
465 OnCRAAChange();
466 } );
467 }
468
469 bool Validate( wxArrayString& aErrs ) const override
470 {
471 const EDA_ANGLE angle = GetAngleValue( CSA_ANGLE );
472
473 if( angle == ANGLE_0 )
474 {
475 aErrs.push_back( _( "Arc angle must be greater than 0" ) );
476 return false;
477 }
478
479 if( GetIntValue( CRAA_RADIUS ) <= 0 )
480 {
481 aErrs.push_back( _( "Radius must be greater than 0" ) );
482 return false;
483 }
484
488
489 if( start == mid || mid == end )
490 {
491 aErrs.push_back( _( "Arc must have at least 2 distinct points" ) );
492 return false;
493 }
494 else
495 {
496 const VECTOR2D center = CalcArcCenter( start, mid, end );
497
498 const double radius = ( center - start ).EuclideanNorm();
499 const double max_offset = std::max( std::abs( center.x ), std::abs( center.y ) ) + radius;
500 const VECTOR2I center_i = KiROUND( center );
501
502 if( max_offset >= ( std::numeric_limits<VECTOR2I::coord_type>::max() / 2.0 ) || center_i == start
503 || center_i == end )
504 {
505 aErrs.push_back( wxString::Format( _( "Invalid Arc with radius %f and angle %f." ),
506 radius, angle.AsDegrees() ) );
507 return false;
508 }
509 }
510
511 return true;
512 }
513
514 void updateAll() override
515 {
516 updateCSA();
517 updateSME();
518 updateCRAA();
519 }
520
522 {
525 const EDA_ANGLE angle{ GetAngleValue( CSA_ANGLE ) };
526
528 GetShape().SetStart( start );
529 GetShape().SetArcAngleAndEnd( angle );
530
531 updateSME();
532 updateCRAA();
533 }
534
536 {
537 const VECTOR2I center = GetShape().GetCenter();
538 const VECTOR2I start = GetShape().GetStart();
539
542 ChangeValue( CSA_START_X, start.x );
543 ChangeValue( CSA_START_Y, start.y );
544 ChangeAngleValue( CSA_ANGLE, GetShape().GetArcAngle() );
545 }
546
548 {
552
553 GetShape().SetArcGeometry( p0, p1, p2 );
554
555 updateCSA();
556 updateCRAA();
557 }
558
560 {
561 const VECTOR2I p0 = GetShape().GetStart();
562 const VECTOR2I p1 = GetShape().GetArcMid();
563 const VECTOR2I p2 = GetShape().GetEnd();
564
567 ChangeValue( SME_MID_X, p1.x );
568 ChangeValue( SME_MID_Y, p1.y );
569 ChangeValue( SME_END_X, p2.x );
570 ChangeValue( SME_END_Y, p2.y );
571 }
572
574 {
576 const int radius = GetIntValue( CRAA_RADIUS );
577 const EDA_ANGLE start_angle = GetAngleValue( CRAA_ANGLE_START );
578 const EDA_ANGLE end_angle = GetAngleValue( CRAA_ANGLE_END );
579
580 // The angles are the directions of the start/end radius lines, measured in the
581 // same convention as the stored arc (i.e. passing the mid point), so we account
582 // for the sweep direction automatically.
583 const VECTOR2I start_pt = center + GetRotated( VECTOR2I( radius, 0 ), -start_angle );
584 const VECTOR2I end_pt = center + GetRotated( VECTOR2I( radius, 0 ), -end_angle );
585
586 EDA_ANGLE sweep = ( end_angle - start_angle ).Normalize();
587
588 // Equal angles describe a full-circle arc.
589 if( sweep == ANGLE_0 )
590 sweep = ANGLE_360;
591
592 const VECTOR2I mid_pt = center + GetRotated( VECTOR2I( radius, 0 ), -( start_angle + sweep / 2.0 ) );
593
594 GetShape().SetArcGeometry( start_pt, mid_pt, end_pt );
595
596 updateCSA();
597 updateSME();
598 }
599
601 {
602 const PCB_SHAPE& shape = GetShape();
603
604 const VECTOR2I center = shape.GetCenter();
605 const int radius = shape.GetRadius();
606 const VECTOR2I start_pt = shape.GetStart();
607 const VECTOR2I end_pt = shape.GetEnd();
608
612 ChangeAngleValue( CRAA_ANGLE_START, EDA_ANGLE( start_pt - center ).Normalize() );
613 ChangeAngleValue( CRAA_ANGLE_END, EDA_ANGLE( end_pt - center ).Normalize() );
614 }
615};
616
617
619{
620public:
633
634 CIRCLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
635 GEOM_SYNCER( aShape, aBoundCtrls )
636 {
637 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
638 wxASSERT( GetShape().GetShape() == SHAPE_T::CIRCLE );
639
641 [this]()
642 {
644 } );
645
647 [this]()
648 {
650 } );
651 }
652
653 void updateAll() override
654 {
657 }
658
659 bool Validate( wxArrayString& aErrs ) const override
660 {
661 if( GetIntValue( RADIUS ) <= 0 )
662 {
663 aErrs.push_back( _( "Radius must be greater than 0" ) );
664 return false;
665 }
666
667 return true;
668 }
669
671 {
673 const int radius = GetIntValue( RADIUS );
674
677
679 }
680
682 {
683 const VECTOR2I center = GetShape().GetCenter();
684
687 ChangeValue( RADIUS, GetShape().GetRadius() );
688 }
689
691 {
694
696 GetShape().SetEnd( pt );
697
699 }
700
702 {
703 const VECTOR2I center = GetShape().GetCenter();
704 const VECTOR2I pt = GetShape().GetEnd();
705
708 ChangeValue( PT_PT_X, pt.x );
709 ChangeValue( PT_PT_Y, pt.y );
710 }
711};
712
713
718{
719public:
730
731 ELLIPSE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
732 GEOM_SYNCER( aShape, aBoundCtrls )
733 {
734 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
735 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE );
736
738 [this]()
739 {
740 OnChange();
741 } );
742 }
743
744 bool Validate( wxArrayString& aErrs ) const override
745 {
746 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
747 {
748 aErrs.push_back( _( "Major radius must be greater than 0" ) );
749 return false;
750 }
751
752 if( GetIntValue( MINOR_RADIUS ) <= 0 )
753 {
754 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
755 return false;
756 }
757
758 return true;
759 }
760
761 void updateAll() override
762 {
764 const int major = GetShape().GetEllipseMajorRadius();
765 const int minor = GetShape().GetEllipseMinorRadius();
766 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
767
770 ChangeValue( MAJOR_RADIUS, major );
771 ChangeValue( MINOR_RADIUS, minor );
772 ChangeAngleValue( ROTATION, rotation );
773 }
774
775 void OnChange()
776 {
778 const int major = GetIntValue( MAJOR_RADIUS );
779 const int minor = GetIntValue( MINOR_RADIUS );
780 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
781
783 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
784 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
785 GetShape().SetEllipseRotation( rotation );
786 }
787};
788
789
794{
795public:
808
809 ELLIPSE_ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
810 GEOM_SYNCER( aShape, aBoundCtrls )
811 {
812 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
813 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE_ARC );
814
816 [this]()
817 {
818 OnChange();
819 } );
820 }
821
822 bool Validate( wxArrayString& aErrs ) const override
823 {
824 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
825 {
826 aErrs.push_back( _( "Major radius must be greater than 0" ) );
827 return false;
828 }
829
830 if( GetIntValue( MINOR_RADIUS ) <= 0 )
831 {
832 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
833 return false;
834 }
835
836 return true;
837 }
838
839 void updateAll() override
840 {
842 const int major = GetShape().GetEllipseMajorRadius();
843 const int minor = GetShape().GetEllipseMinorRadius();
844 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
845 const EDA_ANGLE start = GetShape().GetEllipseStartAngle();
847
850 ChangeValue( MAJOR_RADIUS, major );
851 ChangeValue( MINOR_RADIUS, minor );
852 ChangeAngleValue( ROTATION, rotation );
855 }
856
857 void OnChange()
858 {
860 const int major = GetIntValue( MAJOR_RADIUS );
861 const int minor = GetIntValue( MINOR_RADIUS );
862 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
863 const EDA_ANGLE start = GetAngleValue( START_ANGLE );
865
867 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
868 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
869 GetShape().SetEllipseRotation( rotation );
872 }
873};
874
875
877{
878public:
892
893 BEZIER_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
894 GEOM_SYNCER( aShape, aBoundCtrls )
895 {
896 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
897 wxASSERT( GetShape().GetShape() == SHAPE_T::BEZIER );
898
900 [this]()
901 {
903 } );
904 }
905
906 void updateAll() override
907 {
908 updateBezier();
909 }
910
912 {
914 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
917
918 GetShape().SetStart( p0 );
919 GetShape().SetEnd( p1 );
920 GetShape().SetBezierC1( c1 );
921 GetShape().SetBezierC2( c2 );
922 }
923
925 {
926 const VECTOR2I p0 = GetShape().GetStart();
927 const VECTOR2I p1 = GetShape().GetEnd();
928 const VECTOR2I c1 = GetShape().GetBezierC1();
929 const VECTOR2I c2 = GetShape().GetBezierC2();
930
931 ChangeValue( START_X, p0.x );
932 ChangeValue( START_Y, p0.y );
933 ChangeValue( END_X, p1.x );
934 ChangeValue( END_Y, p1.y );
935 ChangeValue( CTRL1_X, c1.x );
936 ChangeValue( CTRL1_Y, c1.y );
937 ChangeValue( CTRL2_X, c2.x );
938 ChangeValue( CTRL2_Y, c2.y );
939 }
940};
941
942
944{
945public:
946 DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape );
947 ~DIALOG_SHAPE_PROPERTIES() override = default;
948
949private:
950 bool TransferDataToWindow() override;
951 bool TransferDataFromWindow() override;
952
953 void onRoundedRectChanged( wxCommandEvent& event ) override;
954 void onCornerRadius( wxCommandEvent& event ) override;
955 void onLayerSelection( wxCommandEvent& event ) override;
956 void onTechLayersChanged( wxCommandEvent& event ) override;
957
958 bool Validate() override;
960
962 {
963 bool isCopper = IsCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
964
965 m_netSelector->Enable( isCopper );
966 m_netLabel->Enable( isCopper );
967 }
968
970 {
971 bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
972
973 m_techLayersLabel->Enable( isExtCopper );
974 m_hasSolderMask->Enable( isExtCopper );
975
976 bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue();
977
978 m_solderMaskMarginLabel->Enable( showMaskMargin );
979 m_solderMaskMarginCtrl->Enable( showMaskMargin );
980 m_solderMaskMarginUnit->Enable( showMaskMargin );
981 }
982
983private:
986
990
991 wxBoxSizer* m_endingsSizer;
992 wxStaticText* m_startShapeLabel;
993 wxBitmapComboBox* m_startShapeChoice;
994 wxStaticText* m_endShapeLabel;
995 wxBitmapComboBox* m_endShapeChoice;
996 wxStaticText* m_startLengthLabel;
997 wxTextCtrl* m_startLengthCtrl;
998 wxStaticText* m_startLengthUnits;
999 wxStaticText* m_endLengthLabel;
1000 wxTextCtrl* m_endLengthCtrl;
1001 wxStaticText* m_endLengthUnits;
1002 wxStaticText* m_startWidthLabel;
1003 wxTextCtrl* m_startWidthCtrl;
1004 wxStaticText* m_startWidthUnits;
1005 wxStaticText* m_endWidthLabel;
1006 wxTextCtrl* m_endWidthCtrl;
1007 wxStaticText* m_endWidthUnits;
1008 wxStaticText* m_startStrokeWidthLabel;
1009 wxTextCtrl* m_startStrokeWidthCtrl;
1010 wxStaticText* m_startStrokeWidthUnits;
1011 wxStaticText* m_endStrokeWidthLabel;
1012 wxTextCtrl* m_endStrokeWidthCtrl;
1013 wxStaticText* m_endStrokeWidthUnits;
1014 wxStaticText* m_endingsHelpLabel;
1015
1016 std::unique_ptr<UNIT_BINDER> m_startLength;
1017 std::unique_ptr<UNIT_BINDER> m_startWidth;
1018 std::unique_ptr<UNIT_BINDER> m_startStrokeWidth;
1019 std::unique_ptr<UNIT_BINDER> m_endLength;
1020 std::unique_ptr<UNIT_BINDER> m_endWidth;
1021 std::unique_ptr<UNIT_BINDER> m_endStrokeWidth;
1022
1023 std::vector<BOUND_CONTROL> m_boundCtrls;
1024 std::unique_ptr<GEOM_SYNCER> m_geomSync;
1026};
1027
1028
1029static std::map<SHAPE_T, int> s_lastTabForShape;
1030
1031
1033{
1034 wxCHECK_RET( m_upperSizer, wxT( "Shape properties dialog has no upper sizer" ) );
1035
1036 m_endingsSizer = new wxBoxSizer( wxVERTICAL );
1037
1038 wxGridBagSizer* gbSizerEndings = new wxGridBagSizer( 3, 0 );
1039 gbSizerEndings->SetFlexibleDirection( wxBOTH );
1040 gbSizerEndings->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
1041
1042 m_startShapeLabel = new wxStaticText( this, wxID_ANY, _( "Start Shape:" ) );
1043 m_startShapeLabel->Wrap( -1 );
1044 gbSizerEndings->Add( m_startShapeLabel, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL | wxRIGHT,
1045 5 );
1046
1047 m_startShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1048 nullptr, wxCB_READONLY );
1049 gbSizerEndings->Add( m_startShapeChoice, wxGBPosition( 0, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL | wxEXPAND,
1050 5 );
1051
1052 m_endShapeLabel = new wxStaticText( this, wxID_ANY, _( "End Shape:" ) );
1053 m_endShapeLabel->Wrap( -1 );
1054 gbSizerEndings->Add( m_endShapeLabel, wxGBPosition( 0, 4 ), wxGBSpan( 1, 1 ),
1055 wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5 );
1056
1057 m_endShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1058 nullptr, wxCB_READONLY );
1059 gbSizerEndings->Add( m_endShapeChoice, wxGBPosition( 0, 5 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL | wxEXPAND,
1060 5 );
1061
1062 auto addEndingValue = [&]( int aRow, int aCol, const wxString& aLabel, wxStaticText*& aLabelCtrl,
1063 wxTextCtrl*& aValueCtrl, wxStaticText*& aUnitsCtrl, int aFlags )
1064 {
1065 aLabelCtrl = new wxStaticText( this, wxID_ANY, aLabel );
1066 aLabelCtrl->Wrap( -1 );
1067 gbSizerEndings->Add( aLabelCtrl, wxGBPosition( aRow, aCol ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL | aFlags,
1068 5 );
1069
1070 aValueCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), 0 );
1071 gbSizerEndings->Add( aValueCtrl, wxGBPosition( aRow, aCol + 1 ), wxGBSpan( 1, 1 ),
1072 wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
1073
1074 aUnitsCtrl = new wxStaticText( this, wxID_ANY, _( "unit" ) );
1075 aUnitsCtrl->Wrap( -1 );
1076 aUnitsCtrl->SetMinSize( wxSize( 60, -1 ) );
1077 gbSizerEndings->Add( aUnitsCtrl, wxGBPosition( aRow, aCol + 2 ), wxGBSpan( 1, 1 ),
1078 wxALIGN_CENTER_VERTICAL | wxLEFT, 3 );
1079 };
1080
1081 addEndingValue( 1, 0, _( "Start Length:" ), m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits, wxRIGHT );
1082 addEndingValue( 1, 4, _( "End Length:" ), m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits, wxLEFT | wxRIGHT );
1083 addEndingValue( 2, 0, _( "Start Width:" ), m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits, wxRIGHT );
1084 addEndingValue( 2, 4, _( "End Width:" ), m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits, wxLEFT | wxRIGHT );
1085 addEndingValue( 3, 0, _( "Start Stroke Width:" ), m_startStrokeWidthLabel, m_startStrokeWidthCtrl,
1086 m_startStrokeWidthUnits, wxRIGHT );
1087 addEndingValue( 3, 4, _( "End Stroke Width:" ), m_endStrokeWidthLabel, m_endStrokeWidthCtrl, m_endStrokeWidthUnits,
1088 wxLEFT | wxRIGHT );
1089
1090 gbSizerEndings->AddGrowableCol( 1 );
1091 gbSizerEndings->AddGrowableCol( 5 );
1092
1093 m_endingsSizer->Add( gbSizerEndings, 0, wxEXPAND | wxALL, 5 );
1094
1095 m_endingsHelpLabel = new wxStaticText( this, wxID_ANY, wxEmptyString );
1096 m_endingsHelpLabel->Wrap( -1 );
1097 m_endingsSizer->Add( m_endingsHelpLabel, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5 );
1098
1099 m_upperSizer->Insert( 1, m_endingsSizer, 0, wxEXPAND, 5 );
1100
1101 wxColour fg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
1102 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
1103 wxSize iconSize( 80, 24 );
1104
1105 struct
1106 {
1107 wxString name;
1108 LINE_ENDING_STYLE style;
1109 } shapeItems[] = {
1110 { _( "None" ), LINE_ENDING_STYLE::NONE },
1111 { _( "Arrow" ), LINE_ENDING_STYLE::ARROW },
1112 { _( "Open Arrow" ), LINE_ENDING_STYLE::ARROW_OPEN },
1113 { _( "Circle" ), LINE_ENDING_STYLE::CIRCLE },
1114 { _( "Square" ), LINE_ENDING_STYLE::SQUARE },
1115 };
1116
1117 for( const auto& item : shapeItems )
1118 {
1119 wxBitmap startBmp = MakeLineEndingBitmap( item.style, iconSize, fg, bg, this, false );
1120 wxBitmap endBmp = MakeLineEndingBitmap( item.style, iconSize, fg, bg, this, true );
1121 m_startShapeChoice->Append( item.name, startBmp );
1122 m_endShapeChoice->Append( item.name, endBmp );
1123 }
1124
1125 m_startShapeChoice->SetSelection( 0 );
1126 m_endShapeChoice->SetSelection( 0 );
1127
1128 m_startLength = std::make_unique<UNIT_BINDER>( aParent, m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits );
1129 m_startWidth = std::make_unique<UNIT_BINDER>( aParent, m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits );
1130 m_startStrokeWidth = std::make_unique<UNIT_BINDER>( aParent, m_startStrokeWidthLabel, m_startStrokeWidthCtrl,
1132 m_endLength = std::make_unique<UNIT_BINDER>( aParent, m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits );
1133 m_endWidth = std::make_unique<UNIT_BINDER>( aParent, m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits );
1134 m_endStrokeWidth = std::make_unique<UNIT_BINDER>( aParent, m_endStrokeWidthLabel, m_endStrokeWidthCtrl,
1136}
1137
1138
1141 m_parent( aParent ),
1142 m_item( aShape ),
1147{
1148 SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) );
1149 m_hash_key = TO_UTF8( GetTitle() );
1150
1151 createLineEndingControls( aParent );
1152
1153 wxFont infoFont = KIUI::GetSmallInfoFont( this );
1154 m_techLayersLabel->SetFont( infoFont );
1155
1156 // All the pages exist in the WxFB template, but we'll scrap the ones we don't
1157 // use. Constructing on-demand would work fine too.
1158 std::set<int> shownPages;
1159
1160 const auto showPage =
1161 [&]( wxSizer& aMainSizer, bool aSelect = false )
1162 {
1163 // Get the parent of the sizer, which is the panel
1164 wxWindow* page = aMainSizer.GetContainingWindow();
1165 wxCHECK( page, /* void */ );
1166 page->Layout();
1167
1168 const int pageIdx = m_notebookShapeDefs->FindPage( page );
1169 shownPages.insert( pageIdx );
1170
1171 if( aSelect )
1172 m_notebookShapeDefs->SetSelection( pageIdx );
1173 };
1174
1175 switch( m_item->GetShape() )
1176 {
1177 case SHAPE_T::RECTANGLE:
1178 // For all these functions, it's very important that the fields are added in the same order
1179 // as the CTRL_IDX enums in the GEOM_SYNCER classes.
1180 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1181 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 3, _( "End Point" ), false, m_boundCtrls );
1182
1183 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1184 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1185
1186 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 0, _( "Center" ), false, m_boundCtrls );
1187 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1188
1189 m_geomSync = std::make_unique<RECTANGLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1190
1191 showPage( *m_gbsRectangleByCorners, true );
1192 showPage( *m_gbsRectangleByCornerSize );
1193 showPage( *m_gbsRectangleByCenterSize );
1194 break;
1195
1196 case SHAPE_T::SEGMENT:
1197
1198 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1199 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 3, _( "End Point" ), false, m_boundCtrls );
1200
1201 AddXYPointToSizer( *aParent, *m_gbsLineByLengthAngle, 0, 0, _( "Start Point" ), false, m_boundCtrls);
1204
1205 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1206 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1207
1208 m_geomSync = std::make_unique<LINE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1209
1210 showPage( *m_gbsLineByEnds, true );
1211 showPage( *m_gbsLineByLengthAngle );
1212 showPage( *m_gbsLineByStartMid );
1213 break;
1214
1215 case SHAPE_T::ARC:
1216 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 0, _( "Center" ), false, m_boundCtrls);
1217 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 3, _( "Start Point" ), false, m_boundCtrls);
1218 AddFieldToSizer( *aParent, *m_gbsArcByCSA, 3, 0, _( "Included Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
1219
1220 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1221 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1222 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 3, 0, _( "End Point" ), false, m_boundCtrls );
1223
1224 AddXYPointToSizer( *aParent, *m_gbsArcByCRAA, 0, 0, _( "Center" ), false, m_boundCtrls );
1225 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 0, 3, _( "Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1226 m_boundCtrls );
1227 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 1, 3, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1228 m_boundCtrls );
1229 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 2, 3, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1230 m_boundCtrls );
1231
1232 m_geomSync = std::make_unique<ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1233
1234 showPage( *m_gbsArcByCSA, true );
1235 showPage( *m_gbsArcBySME );
1236 showPage( *m_gbsArcByCRAA );
1237 break;
1238
1239 case SHAPE_T::CIRCLE:
1240 AddXYPointToSizer( *aParent, *m_gbsCircleCenterRadius, 0, 0, _( "Center" ), false, m_boundCtrls);
1242
1243 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 0, _( "Center" ), false, m_boundCtrls );
1244 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 3, _( "Point on Circle" ), false, m_boundCtrls );
1245
1246 m_geomSync = std::make_unique<CIRCLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1247
1248 showPage( *m_gbsCircleCenterRadius, true );
1249 showPage( *m_gbsCircleCenterPoint );
1250 break;
1251
1252 case SHAPE_T::BEZIER:
1253 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1254 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 3, _( "End Point" ), false, m_boundCtrls );
1255 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 0, _( "Control Point 1" ), false, m_boundCtrls );
1256 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 3, _( "Control Point 2" ), false, m_boundCtrls );
1257
1258 m_geomSync = std::make_unique<BEZIER_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1259
1260 showPage( *m_gbsBezier, TRUE );
1261 break;
1262
1263 case SHAPE_T::POLY:
1264 m_notebookShapeDefs->Hide();
1265 // Nothing to do here...yet
1266 break;
1267
1268 case SHAPE_T::ELLIPSE:
1269 AddXYPointToSizer( *aParent, *m_gbsEllipse, 0, 0, _( "Center" ), false, m_boundCtrls );
1270 AddFieldToSizer( *aParent, *m_gbsEllipse, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1271 m_boundCtrls );
1272 AddFieldToSizer( *aParent, *m_gbsEllipse, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1273 m_boundCtrls );
1274 AddFieldToSizer( *aParent, *m_gbsEllipse, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1275 m_boundCtrls );
1276
1277 m_geomSync = std::make_unique<ELLIPSE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1278
1279 showPage( *m_gbsEllipse, true );
1280 break;
1281
1283 AddXYPointToSizer( *aParent, *m_gbsEllipseArc, 0, 0, _( "Center" ), false, m_boundCtrls );
1284 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1285 m_boundCtrls );
1286 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1287 m_boundCtrls );
1288 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1289 m_boundCtrls );
1290 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 6, 0, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1291 m_boundCtrls );
1292 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 7, 0, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1293 m_boundCtrls );
1294
1295 m_geomSync = std::make_unique<ELLIPSE_ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1296
1297 showPage( *m_gbsEllipseArc, true );
1298 break;
1299
1300 case SHAPE_T::UNDEFINED:
1301 wxFAIL_MSG( "Undefined shape" );
1302 break;
1303 }
1304
1305 // Remove any tabs not used (Hide() doesn't work on Windows)
1306 for( int i = (int) m_notebookShapeDefs->GetPageCount() - 1; i >= 0; --i )
1307 {
1308 if( shownPages.count( i ) == 0 )
1309 m_notebookShapeDefs->RemovePage( i );
1310 }
1311
1312 // Used the last saved tab if any
1313 if( s_lastTabForShape.count( m_item->GetShape() ) > 0
1314 && s_lastTabForShape[m_item->GetShape()] < (int) m_notebookShapeDefs->GetPageCount()
1315 && s_lastTabForShape[m_item->GetShape()] >= 0 )
1316 {
1317 m_notebookShapeDefs->SetSelection( s_lastTabForShape[m_item->GetShape()] );
1318 }
1319
1320 // Find the first control in the shown tab
1321 wxWindow* tabPanel = m_notebookShapeDefs->GetCurrentPage();
1322
1323 for( size_t i = 0; i < m_boundCtrls.size(); ++i )
1324 {
1325 if( m_boundCtrls[i].m_Ctrl->IsDescendant( tabPanel ) )
1326 {
1327 m_boundCtrls[i].m_Ctrl->SetFocus();
1328 break;
1329 }
1330 }
1331
1332 // Do not allow locking items in the footprint editor
1333 m_locked->Show( dynamic_cast<PCB_EDIT_FRAME*>( aParent ) != nullptr );
1334
1335 // Configure the layers list selector
1336 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1337 {
1338 // In the footprint editor, turn off the layers that the footprint doesn't have
1339 const LSET& brdLayers = aParent->GetBoard()->GetEnabledLayers();
1340 LSET forbiddenLayers = LSET::AllLayersMask() & ~brdLayers;
1341
1342 m_LayerSelectionCtrl->SetNotAllowedLayerSet( forbiddenLayers );
1343 }
1344
1345 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
1346 m_lineStyleCombo->Append( lineStyleDesc.name, KiBitmapBundle( lineStyleDesc.bitmap ) );
1347
1348 m_LayerSelectionCtrl->SetLayersHotkeys( false );
1349 m_LayerSelectionCtrl->SetBoardFrame( m_parent );
1350 m_LayerSelectionCtrl->Resync();
1351
1352 m_netSelector->SetNetInfo( &aParent->GetBoard()->GetNetInfo() );
1353
1354 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1355 {
1356 m_netLabel->Hide();
1357 m_netSelector->Hide();
1358 }
1359
1360 if( m_item->GetShape() == SHAPE_T::ARC || m_item->GetShape() == SHAPE_T::SEGMENT
1361 || m_item->GetShape() == SHAPE_T::BEZIER || m_item->GetShape() == SHAPE_T::ELLIPSE_ARC )
1362 {
1363 m_fillLabel->Show( false );
1364 m_fillCtrl->Show( false );
1365 }
1366
1367 if( m_item->GetShape() != SHAPE_T::RECTANGLE )
1368 {
1369 m_cbRoundRect->Show( false );
1370 m_cornerRadius.Show( false );
1371 }
1372
1373 // Only show line ending controls for open shapes
1374 m_endingsSizer->Show( isOpenShape( m_item->GetShape() ) );
1375
1376 m_endingsHelpLabel->SetFont( KIUI::GetSmallInfoFont( this ).Italic() );
1377 m_endingsHelpLabel->SetLabel( wxString::Format( _( "Shape sizes of 0 = auto (%g\u00d7 line width)." ),
1379
1381
1382 // Now all widgets have the size fixed, call FinishDialogSettings
1384}
1385
1386
1388{
1389 wxCHECK_RET( aShape, wxT( "ShowGraphicItemPropertiesDialog() error: NULL item" ) );
1390
1391 DIALOG_SHAPE_PROPERTIES dlg( this, aShape );
1392
1393 if( dlg.ShowQuasiModal() == wxID_OK )
1394 {
1395 if( aShape->IsOnLayer( GetActiveLayer() ) )
1396 {
1397 DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
1398 drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
1399 }
1400 }
1401}
1402
1403
1405{
1406 if( !m_cbRoundRect->GetValue() )
1407 m_cornerRadius.ChangeValue( wxEmptyString );
1408}
1409
1410
1411void DIALOG_SHAPE_PROPERTIES::onCornerRadius( wxCommandEvent &event )
1412{
1413 m_cbRoundRect->SetValue( true );
1414}
1415
1416
1418{
1419 if( m_LayerSelectionCtrl->GetLayerSelection() >= 0 )
1420 enableNetInfo();
1421
1423}
1424
1425
1427{
1429}
1430
1431
1433{
1434 if( !m_item )
1435 return false;
1436
1437 // Not all shapes have a syncer (e.g. polygons)
1438 if( m_geomSync )
1440
1441 m_fillCtrl->SetSelection( m_item->GetFillModeProp() );
1442 m_locked->SetValue( m_item->IsLocked() );
1443
1445 {
1446 if( m_item->GetCornerRadius() > 0 )
1447 {
1448 m_cbRoundRect->SetValue( true );
1450 }
1451 else
1452 {
1453 m_cbRoundRect->SetValue( false );
1454 m_cornerRadius.ChangeValue( wxEmptyString );
1455 }
1456 }
1457
1458 m_thickness.SetValue( m_item->GetStroke().GetWidth() );
1459
1460 int style = static_cast<int>( m_item->GetStroke().GetLineStyle() );
1461
1462 if( style >= 0 && style < (int) lineTypeNames.size() )
1463 m_lineStyleCombo->SetSelection( style );
1464 else
1465 m_lineStyleCombo->SetSelection( 0 );
1466
1467 m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
1468
1469 m_hasSolderMask->SetValue( m_item->HasSolderMask() );
1470
1471 if( m_item->GetLocalSolderMaskMargin().has_value() )
1472 m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() );
1473 else
1474 m_solderMaskMargin.SetValue( wxEmptyString );
1475
1476 if( m_parent->GetFrameType() == FRAME_PCB_EDITOR )
1477 {
1478 int net = m_item->GetNetCode();
1479
1480 if( net >= 0 )
1481 {
1482 m_netSelector->SetSelectedNetcode( net );
1483 }
1484 else
1485 {
1486 m_netSelector->SetIndeterminateString( INDETERMINATE_STATE );
1487 m_netSelector->SetIndeterminate();
1488 }
1489 }
1490
1491 enableNetInfo();
1493
1494 // Line endings (only for open shapes)
1495 if( isOpenShape( m_item->GetShape() ) )
1496 {
1497 m_startShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetStartEndingStyle() ) );
1498 m_endShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetEndEndingStyle() ) );
1499 m_startLength->SetValue( m_item->GetStartEndingLength() );
1500 m_startWidth->SetValue( m_item->GetStartEndingWidth() );
1501 m_startStrokeWidth->SetValue( m_item->GetStartEndingStrokeWidth() );
1502 m_endLength->SetValue( m_item->GetEndEndingLength() );
1503 m_endWidth->SetValue( m_item->GetEndEndingWidth() );
1504 m_endStrokeWidth->SetValue( m_item->GetEndEndingStrokeWidth() );
1505 }
1506
1507 return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow();
1508}
1509
1510
1512{
1513 if( !DIALOG_SHAPE_PROPERTIES_BASE::TransferDataFromWindow() )
1514 return false;
1515
1516 if( !m_item )
1517 return true;
1518
1519 int layer = m_LayerSelectionCtrl->GetLayerSelection();
1520
1521 BOARD_COMMIT commit( m_parent );
1522 commit.Modify( m_item );
1523
1524 bool pushCommit = ( m_item->GetEditFlags() == 0 );
1525
1526 // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
1527 // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
1528 if( !pushCommit )
1529 m_item->SetFlags( IN_EDIT );
1530
1532
1533 bool wasLocked = m_item->IsLocked();
1534
1535 if( m_item->GetShape() == SHAPE_T::RECTANGLE )
1536 m_item->SetCornerRadius( m_cbRoundRect->GetValue() ? m_cornerRadius.GetIntValue() : 0 );
1537
1538 m_item->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() );
1539 m_item->SetLocked( m_locked->GetValue() );
1540
1541 m_item->SetWidth( m_thickness.GetIntValue() );
1542
1543 auto it = lineTypeNames.begin();
1544 std::advance( it, m_lineStyleCombo->GetSelection() );
1545
1546 if( it == lineTypeNames.end() )
1547 m_item->SetLineStyle( LINE_STYLE::SOLID );
1548 else
1549 m_item->SetLineStyle( it->first );
1550
1551 m_item->SetLayer( ToLAYER_ID( layer ) );
1552
1553 m_item->SetHasSolderMask( m_hasSolderMask->GetValue() );
1554
1555 if( m_solderMaskMargin.IsNull() )
1556 m_item->SetLocalSolderMaskMargin( {} );
1557 else
1558 m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() );
1559
1560 // Line endings (only for open shapes)
1561 if( isOpenShape( m_item->GetShape() ) )
1562 {
1563 int startSel = m_startShapeChoice->GetSelection();
1564
1565 if( startSel >= 0 && startSel < LINE_ENDING::s_defaultChoiceCount )
1566 m_item->SetStartEndingStyle( LINE_ENDING::s_defaultChoiceOrder[startSel] );
1567
1568 int endSel = m_endShapeChoice->GetSelection();
1569
1570 if( endSel >= 0 && endSel < LINE_ENDING::s_defaultChoiceCount )
1571 m_item->SetEndEndingStyle( LINE_ENDING::s_defaultChoiceOrder[endSel] );
1572
1573 if( !m_startLength->IsIndeterminate() )
1574 m_item->SetStartEndingLength( std::max( 0, m_startLength->GetIntValue() ) );
1575
1576 if( !m_startWidth->IsIndeterminate() )
1577 m_item->SetStartEndingWidth( std::max( 0, m_startWidth->GetIntValue() ) );
1578
1579 if( !m_startStrokeWidth->IsIndeterminate() )
1580 m_item->SetStartEndingStrokeWidth( std::max( 0, m_startStrokeWidth->GetIntValue() ) );
1581
1582 if( !m_endLength->IsIndeterminate() )
1583 m_item->SetEndEndingLength( std::max( 0, m_endLength->GetIntValue() ) );
1584
1585 if( !m_endWidth->IsIndeterminate() )
1586 m_item->SetEndEndingWidth( std::max( 0, m_endWidth->GetIntValue() ) );
1587
1588 if( !m_endStrokeWidth->IsIndeterminate() )
1589 m_item->SetEndEndingStrokeWidth( std::max( 0, m_endStrokeWidth->GetIntValue() ) );
1590 }
1591
1592 m_item->RebuildBezierToSegmentsPointsList( m_item->GetMaxError() );
1593
1594 if( m_item->IsOnCopperLayer() )
1595 m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
1596 else
1597 m_item->SetNetCode( -1 );
1598
1599 if( pushCommit )
1600 commit.Push( _( "Edit Shape Properties" ) );
1601
1602 // Save the tab
1603 s_lastTabForShape[m_item->GetShape()] = m_notebookShapeDefs->GetSelection();
1604
1605 // Notify clients which treat locked and unlocked items differently (ie: POINT_EDITOR)
1606 if( wasLocked != m_item->IsLocked() )
1607 m_parent->GetToolManager()->PostEvent( EVENTS::SelectedEvent );
1608
1609 return true;
1610}
1611
1612
1614{
1615 wxArrayString errors;
1616
1617 if( !DIALOG_SHAPE_PROPERTIES_BASE::Validate() )
1618 return false;
1619
1620 if( m_geomSync )
1621 m_geomSync->Validate( errors );
1622
1623 // Type specific checks.
1624 switch( m_item->GetShape() )
1625 {
1626 case SHAPE_T::ARC:
1627 if( m_thickness.GetValue() <= 0 )
1628 errors.Add( _( "Line width must be greater than zero." ) );
1629 break;
1630
1631 case SHAPE_T::CIRCLE:
1632 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1633 errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) );
1634
1635 break;
1636
1637 case SHAPE_T::RECTANGLE:
1638 {
1639 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1640 errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) );
1641
1642 const RECTANGLE_GEOM_SYNCER* rectGeomSync = static_cast<RECTANGLE_GEOM_SYNCER*>( m_geomSync.get() );
1643 int shortSide = std::min( rectGeomSync->GetRectangleWidth(), rectGeomSync->GetRectangleHeight() );
1644
1645 if( m_cbRoundRect->GetValue() && m_cornerRadius.GetIntValue() * 2 > shortSide )
1646 {
1647 errors.Add( _( "Corner radius must be less than or equal to half the smaller side." ) );
1648 m_cornerRadius.SetValue( KiROUND( shortSide / 2.0 ) );
1649 }
1650
1651 break;
1652 }
1653
1654 case SHAPE_T::POLY:
1655 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1656 errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) );
1657
1658 break;
1659
1660 case SHAPE_T::SEGMENT:
1661 if( m_thickness.GetValue() <= 0 )
1662 errors.Add( _( "Line width must be greater than zero." ) );
1663
1664 break;
1665
1666 case SHAPE_T::BEZIER:
1667 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1668 errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) );
1669
1670 break;
1671
1672 case SHAPE_T::ELLIPSE:
1673 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1674 errors.Add( _( "Line width must be greater than zero for an unfilled ellipse." ) );
1675
1676 break;
1677
1679 if( m_thickness.GetValue() <= 0 )
1680 errors.Add( _( "Line width must be greater than zero." ) );
1681
1682 break;
1683
1684 default:
1685 UNIMPLEMENTED_FOR( m_item->SHAPE_T_asString() );
1686 break;
1687 }
1688
1689 if( errors.GetCount() )
1690 {
1691 HTML_MESSAGE_BOX dlg( this, _( "Error List" ) );
1692 dlg.ListSet( errors );
1693 dlg.ShowModal();
1694 }
1695
1696 return errors.GetCount() == 0;
1697}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
ARC_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool Validate(wxArrayString &aErrs) const override
BEZIER_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool IsLocked() const override
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1207
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1183
bool Validate(wxArrayString &aErrs) const override
CIRCLE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
DIALOG_SHAPE_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("%s Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
std::unique_ptr< UNIT_BINDER > m_endWidth
wxBitmapComboBox * m_startShapeChoice
std::unique_ptr< UNIT_BINDER > m_startWidth
void createLineEndingControls(SCH_BASE_FRAME *aParent)
wxBitmapComboBox * m_endShapeChoice
DIALOG_SHAPE_PROPERTIES(SCH_BASE_FRAME *aParent, SCH_SHAPE *aShape)
std::unique_ptr< UNIT_BINDER > m_startStrokeWidth
std::unique_ptr< UNIT_BINDER > m_startLength
std::unique_ptr< UNIT_BINDER > m_endLength
bool TransferDataToWindow() override
void onCornerRadius(wxCommandEvent &event) override
~DIALOG_SHAPE_PROPERTIES() override=default
void onRoundedRectChanged(wxCommandEvent &event) override
std::unique_ptr< UNIT_BINDER > m_endStrokeWidth
void onLayerSelection(wxCommandEvent &event) override
void onTechLayersChanged(wxCommandEvent &event) override
bool TransferDataFromWindow() override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
Tool responsible for drawing graphical elements like lines, arcs, circles, etc.
void SetStroke(const STROKE_PARAMS &aStroke, PCB_LAYER_ID aLayer)
double AsDegrees() const
Definition eda_angle.h:116
UI_FILL_MODE GetFillModeProp() const
int GetEllipseMinorRadius() const
Definition eda_shape.h:395
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:368
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
void SetCenter(const VECTOR2I &aCenter)
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:423
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:404
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
void SetRadius(int aX)
Definition eda_shape.h:350
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:414
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:365
int GetCornerRadius() const
VECTOR2I GetArcMid() const
ELLIPSE_ARC_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool Validate(wxArrayString &aErrs) const override
bool Validate(wxArrayString &aErrs) const override
ELLIPSE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
static const TOOL_EVENT SelectedEvent
Definition actions.h:343
void ChangeValue(size_t aIndex, int aValue)
wxTextCtrl * GetCtrl(size_t aIndex) const
EDA_ANGLE GetAngleValue(size_t aIndex) const
void BindCtrls(size_t aFrom, size_t aTo, std::function< void()> aCb)
virtual void updateAll()=0
int GetIntValue(size_t aIndex) const
virtual bool Validate(wxArrayString &aErrs) const
const PCB_SHAPE & GetShape() const
void ChangeAngleValue(size_t aIndex, const EDA_ANGLE &aValue)
std::vector< BOUND_CONTROL > & m_boundCtrls
GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
void ListSet(const wxString &aList)
Add a list of items.
static constexpr double DEFAULT_RATIO_LENGTH
static const int s_defaultChoiceCount
static const LINE_ENDING_STYLE s_defaultChoiceOrder[]
Dropdown display order for line ending style choosers.
Definition line_ending.h:38
static int StyleToChoiceIndex(LINE_ENDING_STYLE aStyle)
Return the dropdown index for the given style, or 0 (NONE) if not found.
LINE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:637
Common, abstract interface for edit frames.
void ShowGraphicItemPropertiesDialog(PCB_SHAPE *aShape)
virtual PCB_LAYER_ID GetActiveLayer() const
BOARD * GetBoard() const
The main frame for Pcbnew.
void SetEllipseCenter(const VECTOR2I &aPt) override
void SetBezierC1(const VECTOR2I &aPt) override
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Definition pcb_shape.h:107
void SetEllipseStartAngle(const EDA_ANGLE &aA) override
void SetEllipseEndAngle(const EDA_ANGLE &aA) override
void SetEnd(const VECTOR2I &aEnd) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
void SetEllipseRotation(const EDA_ANGLE &aA) override
void SetEllipseMinorRadius(int aR) override
STROKE_PARAMS GetStroke() const override
void SetStart(const VECTOR2I &aStart) override
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
void SetBezierC2(const VECTOR2I &aPt) override
void SetEllipseMajorRadius(int aR) override
Class that keeps a rectangle's various fields all up to date.
bool Validate(wxArrayString &aErrs) const override
RECTANGLE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
TOOL_MANAGER * m_toolManager
virtual void ChangeValue(int aValue)
Set new value (in Internal Units) for the text field, taking care of units conversion WITHOUT trigger...
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:428
UI_FILL_MODE
Definition eda_fill.h:41
@ SOLID
Definition eda_fill.h:43
#define IN_EDIT
Item currently edited.
SHAPE_T
Definition eda_shape.h:54
@ UNDEFINED
Definition eda_shape.h:55
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
void AddXYPointToSizer(EDA_DRAW_FRAME &aFrame, wxGridBagSizer &aSizer, int row, int col, const wxString &aName, bool aRelative, std::vector< BOUND_CONTROL > &aBoundCtrls)
void AddFieldToSizer(EDA_DRAW_FRAME &aFrame, wxGridBagSizer &aSizer, int row, int col, const wxString &aName, ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType, bool aIsAngle, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:714
LINE_ENDING_STYLE
Line ending styles for graphic lines, arcs, and beziers.
Definition line_ending.h:44
wxBitmap MakeLineEndingBitmap(LINE_ENDING_STYLE aStyle, const wxSize &aSize, const wxColour &aForeground, const wxColour &aBackground, wxWindow *aWindow, bool aShapeOnRight)
Generate a DPI-aware bitmap icon showing a line ending style.
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:92
KICOMMON_API wxFont GetSmallInfoFont(wxWindow *aWindow)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
static std::map< SHAPE_T, int > s_lastTabForShape
static bool isOpenShape(SHAPE_T aShape)
static bool isCopper(const PNS::ITEM *aItem)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
Conversion map between LINE_STYLE values and style names displayed.
VECTOR2I center
int radius
VECTOR2I end
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:73
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:562
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682