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 <bitmaps.h>
29#include <pcb_base_edit_frame.h>
30#include <pcb_edit_frame.h>
31#include <board_commit.h>
36#include <string_utils.h>
37#include <tool/tool_manager.h>
38#include <tool/actions.h>
39#include <pcb_shape.h>
40#include <macros.h>
41#include <algorithm>
42#include <cstdlib>
43#include <widgets/unit_binder.h>
44
46#include <tools/drawing_tool.h>
47#include <line_ending.h>
49
50
60class GEOM_SYNCER : public wxEvtHandler
61{
62public:
63 GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
64 m_shape( aShape ),
65 m_boundCtrls( aBoundCtrls )
66 {
67 }
68
69 void BindCtrls( size_t aFrom, size_t aTo, std::function<void()> aCb )
70 {
71 wxCHECK( aFrom < m_boundCtrls.size(), /* void */ );
72 wxCHECK( aTo < m_boundCtrls.size(), /* void */ );
73
74 for( size_t i = aFrom; i <= aTo; ++i )
75 {
76 m_boundCtrls[i].m_Ctrl->Bind( wxEVT_TEXT,
77 [aCb]( wxCommandEvent& aEvent )
78 {
79 aCb();
80 } );
81 }
82 }
83
84 void SetShape( PCB_SHAPE& aShape )
85 {
86 m_shape = aShape;
87 updateAll();
88 }
89
90 virtual bool Validate( wxArrayString& aErrs ) const { return true; }
91
92protected:
93 virtual void updateAll() = 0;
94
95 wxTextCtrl* GetCtrl( size_t aIndex ) const
96 {
97 wxCHECK( aIndex < m_boundCtrls.size(), nullptr );
98 return m_boundCtrls[aIndex].m_Ctrl;
99 }
100
101 int GetIntValue( size_t aIndex ) const
102 {
103 wxCHECK( aIndex < m_boundCtrls.size(), 0.0 );
104 return static_cast<int>( m_boundCtrls[aIndex].m_Binder->GetValue() );
105 }
106
107 EDA_ANGLE GetAngleValue( size_t aIndex ) const
108 {
109 wxCHECK( aIndex < m_boundCtrls.size(), EDA_ANGLE() );
110 return m_boundCtrls[aIndex].m_Binder->GetAngleValue();
111 }
112
113 void ChangeValue( size_t aIndex, int aValue )
114 {
115 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
116 m_boundCtrls[aIndex].m_Binder->ChangeValue( aValue );
117 }
118
119 void ChangeAngleValue( size_t aIndex, const EDA_ANGLE& aValue )
120 {
121 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
122 m_boundCtrls[aIndex].m_Binder->ChangeAngleValue( aValue );
123 }
124
126
127 const PCB_SHAPE& GetShape() const { return m_shape; }
128
129private:
131 std::vector<BOUND_CONTROL>& m_boundCtrls;
132};
133
134
139{
140public:
158
159 RECTANGLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
160 GEOM_SYNCER( aShape, aBoundCtrls )
161 {
162 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
163 wxASSERT( GetShape().GetShape() == SHAPE_T::RECTANGLE );
164
166 [this]()
167 {
169 } );
170
172 [this]()
173 {
175 } );
176
178 [this]()
179 {
181 } );
182 }
183
184 bool Validate( wxArrayString& aErrs ) const override
185 {
187 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
188
189 if( p0 == p1 )
190 {
191 aErrs.push_back( _( "Rectangle cannot be zero-sized." ) );
192 return false;
193 }
194
195 return true;
196 }
197
199
201
202 void updateAll() override
203 {
207 }
208
210 {
212 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
213
214 GetShape().SetStart( p0 );
215 GetShape().SetEnd( p1 );
216
219 }
220
222 {
223 const VECTOR2I p0 = GetShape().GetStart();
224 const VECTOR2I p1 = GetShape().GetEnd();
225
226 ChangeValue( START_X, p0.x );
227 ChangeValue( START_Y, p0.y );
228 ChangeValue( END_X, p1.x );
229 ChangeValue( END_Y, p1.y );
230 }
231
233 {
235 const VECTOR2I size{ GetIntValue( CORNER_W ), GetIntValue( CORNER_H ) };
236
237 GetShape().SetStart( p0 );
238 GetShape().SetEnd( p0 + size );
239
242 }
243
253
255 {
257 const VECTOR2I size = { GetIntValue( CENTER_W ), GetIntValue( CENTER_H ) };
258
259 GetShape().SetStart( center - size / 2 );
260 GetShape().SetEnd( center + size / 2 );
261
264 }
265
275};
276
277
279{
280public:
300
301 LINE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
302 GEOM_SYNCER( aShape, aBoundCtrls )
303 {
304 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
305 wxASSERT( GetShape().GetShape() == SHAPE_T::SEGMENT );
306
308 [this]()
309 {
310 OnEndsChange();
311 } );
312
314 [this]()
315 {
317 } );
318
320 [this]()
321 {
323 } );
324 }
325
326 void updateAll() override
327 {
328 updateEnds();
329 updatePolar();
331 }
332
334 {
336 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
337
338 GetShape().SetStart( p0 );
339 GetShape().SetEnd( p1 );
340
341 updatePolar();
343 }
344
346 {
347 const VECTOR2I p0 = GetShape().GetStart();
348 const VECTOR2I p1 = GetShape().GetEnd();
349
350 ChangeValue( START_X, p0.x );
351 ChangeValue( START_Y, p0.y );
352 ChangeValue( END_X, p1.x );
353 ChangeValue( END_Y, p1.y );
354 }
355
357 {
359 const int length = GetIntValue( LENGTH );
360 const EDA_ANGLE angle = GetAngleValue( ANGLE );
361
362 const VECTOR2I polar = GetRotated( VECTOR2I{ length, 0 }, angle );
363
364 GetShape().SetStart( p0 );
365 GetShape().SetEnd( p0 + polar );
366
367 updateEnds();
369 }
370
372 {
373 const VECTOR2I p0 = GetShape().GetStart();
374 const VECTOR2I p1 = GetShape().GetEnd();
375
378 ChangeValue( LENGTH, KiROUND( p0.Distance( p1 ) ) );
379 ChangeAngleValue( ANGLE, -EDA_ANGLE( p1 - p0 ) );
380 }
381
383 {
385 const VECTOR2I mid{ GetIntValue( MID_X ), GetIntValue( MID_Y ) };
386
387 GetShape().SetStart( start );
388 GetShape().SetEnd( mid - ( start - mid ) );
389
390 updateEnds();
391 updatePolar();
392 }
393
395 {
396 const VECTOR2I s = GetShape().GetStart();
397 const VECTOR2I c = GetShape().GetCenter();
398
399 ChangeValue( MID_X, c.x );
400 ChangeValue( MID_Y, c.y );
403 }
404};
405
406
408{
409public:
411 {
412 // CSA: center, start, angle
418
419 // SME: start, mid, end
426
427 // CRAA: center, radius, angle (start), angle (end)
433
435 };
436
437 ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
438 GEOM_SYNCER( aShape, aBoundCtrls )
439 {
440 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
441 wxASSERT( GetShape().GetShape() == SHAPE_T::ARC );
442
444 [this]()
445 {
446 OnCSAChange();
447 } );
448
450 [this]()
451 {
452 OnSMEChange();
453 } );
454
456 [this]()
457 {
458 OnCRAAChange();
459 } );
460 }
461
462 bool Validate( wxArrayString& aErrs ) const override
463 {
464 const EDA_ANGLE angle = GetAngleValue( CSA_ANGLE );
465
466 if( angle == ANGLE_0 )
467 {
468 aErrs.push_back( _( "Arc angle must be greater than 0" ) );
469 return false;
470 }
471
472 if( GetIntValue( CRAA_RADIUS ) <= 0 )
473 {
474 aErrs.push_back( _( "Radius must be greater than 0" ) );
475 return false;
476 }
477
481
482 if( start == mid || mid == end )
483 {
484 aErrs.push_back( _( "Arc must have at least 2 distinct points" ) );
485 return false;
486 }
487 else
488 {
489 const VECTOR2D center = CalcArcCenter( start, mid, end );
490
491 const double radius = ( center - start ).EuclideanNorm();
492 const double max_offset = std::max( std::abs( center.x ), std::abs( center.y ) ) + radius;
493 const VECTOR2I center_i = KiROUND( center );
494
495 if( max_offset >= ( std::numeric_limits<VECTOR2I::coord_type>::max() / 2.0 ) || center_i == start
496 || center_i == end )
497 {
498 aErrs.push_back( wxString::Format( _( "Invalid Arc with radius %f and angle %f." ),
499 radius, angle.AsDegrees() ) );
500 return false;
501 }
502 }
503
504 return true;
505 }
506
507 void updateAll() override
508 {
509 updateCSA();
510 updateSME();
511 updateCRAA();
512 }
513
515 {
518 const EDA_ANGLE angle{ GetAngleValue( CSA_ANGLE ) };
519
521 GetShape().SetStart( start );
522 GetShape().SetArcAngleAndEnd( angle );
523
524 updateSME();
525 updateCRAA();
526 }
527
529 {
530 const VECTOR2I center = GetShape().GetCenter();
531 const VECTOR2I start = GetShape().GetStart();
532
535 ChangeValue( CSA_START_X, start.x );
536 ChangeValue( CSA_START_Y, start.y );
537 ChangeAngleValue( CSA_ANGLE, GetShape().GetArcAngle() );
538 }
539
541 {
545
546 // Collinear typed points have no arc, keep the previous geometry
547 if( !IsStraightArc( p0, p1, p2 ) )
548 GetShape().SetArcGeometry( p0, p1, p2 );
549
550 updateCSA();
551 updateCRAA();
552 }
553
555 {
556 const VECTOR2I p0 = GetShape().GetStart();
557 const VECTOR2I p1 = GetShape().GetArcMid();
558 const VECTOR2I p2 = GetShape().GetEnd();
559
562 ChangeValue( SME_MID_X, p1.x );
563 ChangeValue( SME_MID_Y, p1.y );
564 ChangeValue( SME_END_X, p2.x );
565 ChangeValue( SME_END_Y, p2.y );
566 }
567
569 {
571 const int radius = GetIntValue( CRAA_RADIUS );
572 const EDA_ANGLE start_angle = GetAngleValue( CRAA_ANGLE_START );
573 const EDA_ANGLE end_angle = GetAngleValue( CRAA_ANGLE_END );
574
575 // The angles are the directions of the start/end radius lines, measured in the
576 // same convention as the stored arc (i.e. passing the mid point), so we account
577 // for the sweep direction automatically.
578 const VECTOR2I start_pt = center + GetRotated( VECTOR2I( radius, 0 ), -start_angle );
579 const VECTOR2I end_pt = center + GetRotated( VECTOR2I( radius, 0 ), -end_angle );
580
581 EDA_ANGLE sweep = ( end_angle - start_angle ).Normalize();
582
583 // Equal angles describe a full-circle arc.
584 if( sweep == ANGLE_0 )
586
587 const VECTOR2I mid_pt = center + GetRotated( VECTOR2I( radius, 0 ), -( start_angle + sweep / 2.0 ) );
588
589 GetShape().SetArcGeometry( start_pt, mid_pt, end_pt );
590
591 updateCSA();
592 updateSME();
593 }
594
596 {
597 const PCB_SHAPE& shape = GetShape();
598
599 const VECTOR2I center = shape.GetCenter();
600 const int radius = shape.GetRadius();
601 const VECTOR2I start_pt = shape.GetStart();
602 const VECTOR2I end_pt = shape.GetEnd();
603
607 ChangeAngleValue( CRAA_ANGLE_START, EDA_ANGLE( start_pt - center ).Normalize() );
608 ChangeAngleValue( CRAA_ANGLE_END, EDA_ANGLE( end_pt - center ).Normalize() );
609 }
610};
611
612
614{
615public:
628
629 CIRCLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
630 GEOM_SYNCER( aShape, aBoundCtrls )
631 {
632 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
633 wxASSERT( GetShape().GetShape() == SHAPE_T::CIRCLE );
634
636 [this]()
637 {
639 } );
640
642 [this]()
643 {
645 } );
646 }
647
648 void updateAll() override
649 {
652 }
653
654 bool Validate( wxArrayString& aErrs ) const override
655 {
656 if( GetIntValue( RADIUS ) <= 0 )
657 {
658 aErrs.push_back( _( "Radius must be greater than 0" ) );
659 return false;
660 }
661
662 return true;
663 }
664
666 {
668 const int radius = GetIntValue( RADIUS );
669
672
674 }
675
677 {
678 const VECTOR2I center = GetShape().GetCenter();
679
682 ChangeValue( RADIUS, GetShape().GetRadius() );
683 }
684
686 {
689
691 GetShape().SetEnd( pt );
692
694 }
695
697 {
698 const VECTOR2I center = GetShape().GetCenter();
699 const VECTOR2I pt = GetShape().GetEnd();
700
703 ChangeValue( PT_PT_X, pt.x );
704 ChangeValue( PT_PT_Y, pt.y );
705 }
706};
707
708
713{
714public:
725
726 ELLIPSE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
727 GEOM_SYNCER( aShape, aBoundCtrls )
728 {
729 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
730 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE );
731
733 [this]()
734 {
735 OnChange();
736 } );
737 }
738
739 bool Validate( wxArrayString& aErrs ) const override
740 {
741 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
742 {
743 aErrs.push_back( _( "Major radius must be greater than 0" ) );
744 return false;
745 }
746
747 if( GetIntValue( MINOR_RADIUS ) <= 0 )
748 {
749 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
750 return false;
751 }
752
753 return true;
754 }
755
756 void updateAll() override
757 {
759 const int major = GetShape().GetEllipseMajorRadius();
760 const int minor = GetShape().GetEllipseMinorRadius();
761 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
762
765 ChangeValue( MAJOR_RADIUS, major );
766 ChangeValue( MINOR_RADIUS, minor );
767 ChangeAngleValue( ROTATION, rotation );
768 }
769
770 void OnChange()
771 {
773 const int major = GetIntValue( MAJOR_RADIUS );
774 const int minor = GetIntValue( MINOR_RADIUS );
775 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
776
778 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
779 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
780 GetShape().SetEllipseRotation( rotation );
781 }
782};
783
784
789{
790public:
803
804 ELLIPSE_ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
805 GEOM_SYNCER( aShape, aBoundCtrls )
806 {
807 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
808 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE_ARC );
809
811 [this]()
812 {
813 OnChange();
814 } );
815 }
816
817 bool Validate( wxArrayString& aErrs ) const override
818 {
819 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
820 {
821 aErrs.push_back( _( "Major radius must be greater than 0" ) );
822 return false;
823 }
824
825 if( GetIntValue( MINOR_RADIUS ) <= 0 )
826 {
827 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
828 return false;
829 }
830
831 return true;
832 }
833
834 void updateAll() override
835 {
837 const int major = GetShape().GetEllipseMajorRadius();
838 const int minor = GetShape().GetEllipseMinorRadius();
839 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
840 const EDA_ANGLE start = GetShape().GetEllipseStartAngle();
842
845 ChangeValue( MAJOR_RADIUS, major );
846 ChangeValue( MINOR_RADIUS, minor );
847 ChangeAngleValue( ROTATION, rotation );
850 }
851
852 void OnChange()
853 {
855 const int major = GetIntValue( MAJOR_RADIUS );
856 const int minor = GetIntValue( MINOR_RADIUS );
857 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
858 const EDA_ANGLE start = GetAngleValue( START_ANGLE );
860
862 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
863 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
864 GetShape().SetEllipseRotation( rotation );
867 }
868};
869
870
872{
873public:
887
888 BEZIER_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
889 GEOM_SYNCER( aShape, aBoundCtrls )
890 {
891 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
892 wxASSERT( GetShape().GetShape() == SHAPE_T::BEZIER );
893
895 [this]()
896 {
898 } );
899 }
900
901 void updateAll() override
902 {
903 updateBezier();
904 }
905
907 {
909 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
912
913 GetShape().SetStart( p0 );
914 GetShape().SetEnd( p1 );
915 GetShape().SetBezierC1( c1 );
916 GetShape().SetBezierC2( c2 );
917 }
918
920 {
921 const VECTOR2I p0 = GetShape().GetStart();
922 const VECTOR2I p1 = GetShape().GetEnd();
923 const VECTOR2I c1 = GetShape().GetBezierC1();
924 const VECTOR2I c2 = GetShape().GetBezierC2();
925
926 ChangeValue( START_X, p0.x );
927 ChangeValue( START_Y, p0.y );
928 ChangeValue( END_X, p1.x );
929 ChangeValue( END_Y, p1.y );
930 ChangeValue( CTRL1_X, c1.x );
931 ChangeValue( CTRL1_Y, c1.y );
932 ChangeValue( CTRL2_X, c2.x );
933 ChangeValue( CTRL2_Y, c2.y );
934 }
935};
936
937
939{
940public:
941 DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape );
942 ~DIALOG_SHAPE_PROPERTIES() override = default;
943
944private:
945 bool TransferDataToWindow() override;
946 bool TransferDataFromWindow() override;
947
948 void onRoundedRectChanged( wxCommandEvent& event ) override;
949 void onCornerRadius( wxCommandEvent& event ) override;
950 void onLayerSelection( wxCommandEvent& event ) override;
951 void onTechLayersChanged( wxCommandEvent& event ) override;
952
953 bool Validate() override;
955
957 {
958 bool isCopper = IsCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
959
960 m_netSelector->Enable( isCopper );
961 m_netLabel->Enable( isCopper );
962 }
963
965 {
966 bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
967
968 m_techLayersLabel->Enable( isExtCopper );
969 m_hasSolderMask->Enable( isExtCopper );
970
971 bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue();
972
973 m_solderMaskMarginLabel->Enable( showMaskMargin );
974 m_solderMaskMarginCtrl->Enable( showMaskMargin );
975 m_solderMaskMarginUnit->Enable( showMaskMargin );
976 }
977
978private:
981
985
986 wxBoxSizer* m_endingsSizer;
987 wxStaticText* m_startShapeLabel;
988 wxBitmapComboBox* m_startShapeChoice;
989 wxStaticText* m_endShapeLabel;
990 wxBitmapComboBox* m_endShapeChoice;
991 wxStaticText* m_startLengthLabel;
992 wxTextCtrl* m_startLengthCtrl;
993 wxStaticText* m_startLengthUnits;
994 wxStaticText* m_endLengthLabel;
995 wxTextCtrl* m_endLengthCtrl;
996 wxStaticText* m_endLengthUnits;
997 wxStaticText* m_startWidthLabel;
998 wxTextCtrl* m_startWidthCtrl;
999 wxStaticText* m_startWidthUnits;
1000 wxStaticText* m_endWidthLabel;
1001 wxTextCtrl* m_endWidthCtrl;
1002 wxStaticText* m_endWidthUnits;
1003 wxStaticText* m_startThicknessLabel;
1004 wxTextCtrl* m_startThicknessCtrl;
1005 wxStaticText* m_startThicknessUnits;
1006 wxStaticText* m_endThicknessLabel;
1007 wxTextCtrl* m_endThicknessCtrl;
1008 wxStaticText* m_endThicknessUnits;
1009 wxStaticText* m_endingsHelpLabel;
1010
1011 std::unique_ptr<UNIT_BINDER> m_startLength;
1012 std::unique_ptr<UNIT_BINDER> m_startWidth;
1013 std::unique_ptr<UNIT_BINDER> m_startThickness;
1014 std::unique_ptr<UNIT_BINDER> m_endLength;
1015 std::unique_ptr<UNIT_BINDER> m_endWidth;
1016 std::unique_ptr<UNIT_BINDER> m_endThickness;
1017
1018 std::vector<BOUND_CONTROL> m_boundCtrls;
1019 std::unique_ptr<GEOM_SYNCER> m_geomSync;
1021};
1022
1023
1024static std::map<SHAPE_T, int> s_lastTabForShape;
1025
1026
1028{
1029 wxCHECK_RET( m_upperSizer, wxT( "Shape properties dialog has no upper sizer" ) );
1030
1031 m_endingsSizer = new wxBoxSizer( wxVERTICAL );
1032
1033 wxGridBagSizer* gbSizerEndings = new wxGridBagSizer( 3, 0 );
1034 gbSizerEndings->SetFlexibleDirection( wxBOTH );
1035 gbSizerEndings->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
1036 gbSizerEndings->SetEmptyCellSize( wxSize( 20, -1 ) );
1037
1038 m_startShapeLabel = new wxStaticText( this, wxID_ANY, _( "Start Shape:" ) );
1039 gbSizerEndings->Add( m_startShapeLabel, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ),
1040 wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
1041
1042 m_startShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1043 nullptr, wxCB_READONLY );
1044 gbSizerEndings->Add( m_startShapeChoice, wxGBPosition( 0, 1 ), wxGBSpan( 1, 2 ),
1045 wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
1046
1047 m_endShapeLabel = new wxStaticText( this, wxID_ANY, _( "End Shape:" ) );
1048 gbSizerEndings->Add( m_endShapeLabel, wxGBPosition( 0, 4 ), wxGBSpan( 1, 1 ),
1049 wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
1050
1051 m_endShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1052 nullptr, wxCB_READONLY );
1053 gbSizerEndings->Add( m_endShapeChoice, wxGBPosition( 0, 5 ), wxGBSpan( 1, 2 ),
1054 wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
1055
1056 auto addEndingValue =
1057 [&]( int aRow, int aCol, const wxString& aLabel, wxStaticText*& aLabelCtrl, wxTextCtrl*& aValueCtrl,
1058 wxStaticText*& aUnitsCtrl, int aFlags )
1059 {
1060 aLabelCtrl = new wxStaticText( this, wxID_ANY, aLabel );
1061 gbSizerEndings->Add( aLabelCtrl, wxGBPosition( aRow, aCol ), wxGBSpan( 1, 1 ),
1062 wxALIGN_CENTER_VERTICAL | wxLEFT | aFlags, 15 );
1063
1064 aValueCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), 0 );
1065 gbSizerEndings->Add( aValueCtrl, wxGBPosition( aRow, aCol + 1 ), wxGBSpan( 1, 1 ),
1066 wxALIGN_CENTER_VERTICAL | wxLEFT | wxEXPAND, 15 );
1067
1068 aUnitsCtrl = new wxStaticText( this, wxID_ANY, _( "unit" ) );
1069 gbSizerEndings->Add( aUnitsCtrl, wxGBPosition( aRow, aCol + 2 ), wxGBSpan( 1, 1 ),
1070 wxALIGN_CENTER_VERTICAL | wxLEFT, 3 );
1071 };
1072
1073 addEndingValue( 1, 0, _( "Length:" ), m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits, wxRIGHT );
1074 addEndingValue( 1, 4, _( "Length:" ), m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits, wxLEFT | wxRIGHT );
1075 addEndingValue( 2, 0, _( "Width:" ), m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits, wxRIGHT );
1076 addEndingValue( 2, 4, _( "Width:" ), m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits, wxLEFT | wxRIGHT );
1077 addEndingValue( 3, 0, _( "Thickness:" ), m_startThicknessLabel, m_startThicknessCtrl, m_startThicknessUnits,
1078 wxRIGHT );
1079 addEndingValue( 3, 4, _( "Thickness:" ), m_endThicknessLabel, m_endThicknessCtrl, m_endThicknessUnits,
1080 wxLEFT | wxRIGHT );
1081
1082 gbSizerEndings->AddGrowableCol( 1 );
1083 gbSizerEndings->AddGrowableCol( 5 );
1084
1085 m_endingsSizer->Add( gbSizerEndings, 0, wxEXPAND | wxALL, 5 );
1086
1087 m_endingsHelpLabel = new wxStaticText( this, wxID_ANY, wxEmptyString );
1088 m_endingsSizer->Add( m_endingsHelpLabel, 0, wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, 5 );
1089
1090 m_upperSizer->Insert( 1, m_endingsSizer, 0, wxEXPAND, 5 );
1091
1092 wxColour fg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
1093 wxSize iconSize = GetTextExtent( wxT( "XXXXXX" ) );
1094
1095 struct
1096 {
1097 wxString name;
1098 LINE_ENDING_STYLE style;
1099 } shapeItems[] = {
1100 { _( "None" ), LINE_ENDING_STYLE::NONE },
1101 { _( "Arrow" ), LINE_ENDING_STYLE::ARROW },
1102 { _( "Open Arrow" ), LINE_ENDING_STYLE::ARROW_OPEN },
1103 { _( "Circle" ), LINE_ENDING_STYLE::CIRCLE },
1104 { _( "Square" ), LINE_ENDING_STYLE::SQUARE },
1105 };
1106
1107 for( const auto& item : shapeItems )
1108 {
1109 wxBitmap startBmp = MakeLineEndingBitmap( item.style, iconSize, fg, this, false );
1110 wxBitmap endBmp = MakeLineEndingBitmap( item.style, iconSize, fg, this, true );
1111 m_startShapeChoice->Append( item.name, startBmp );
1112 m_endShapeChoice->Append( item.name, endBmp );
1113 }
1114
1115 m_startShapeChoice->SetSelection( 0 );
1116 m_endShapeChoice->SetSelection( 0 );
1117
1118 m_startLength = std::make_unique<UNIT_BINDER>( aParent, m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits );
1119 m_startWidth = std::make_unique<UNIT_BINDER>( aParent, m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits );
1120 m_startThickness = std::make_unique<UNIT_BINDER>( aParent, m_startThicknessLabel, m_startThicknessCtrl,
1122 m_endLength = std::make_unique<UNIT_BINDER>( aParent, m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits );
1123 m_endWidth = std::make_unique<UNIT_BINDER>( aParent, m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits );
1124 m_endThickness = std::make_unique<UNIT_BINDER>( aParent, m_endThicknessLabel, m_endThicknessCtrl,
1126}
1127
1128
1131 m_parent( aParent ),
1132 m_item( aShape ),
1137{
1138 SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) );
1139 m_hash_key = TO_UTF8( GetTitle() );
1140
1141 createLineEndingControls( aParent );
1142
1143 wxFont infoFont = KIUI::GetSmallInfoFont( this );
1144 m_techLayersLabel->SetFont( infoFont );
1145
1146 // All the pages exist in the WxFB template, but we'll scrap the ones we don't
1147 // use. Constructing on-demand would work fine too.
1148 std::set<int> shownPages;
1149
1150 const auto showPage =
1151 [&]( wxSizer& aMainSizer, bool aSelect = false )
1152 {
1153 // Get the parent of the sizer, which is the panel
1154 wxWindow* page = aMainSizer.GetContainingWindow();
1155 wxCHECK( page, /* void */ );
1156 page->Layout();
1157
1158 const int pageIdx = m_notebookShapeDefs->FindPage( page );
1159 shownPages.insert( pageIdx );
1160
1161 if( aSelect )
1162 m_notebookShapeDefs->SetSelection( pageIdx );
1163 };
1164
1165 switch( m_item->GetShape() )
1166 {
1167 case SHAPE_T::RECTANGLE:
1168 // For all these functions, it's very important that the fields are added in the same order
1169 // as the CTRL_IDX enums in the GEOM_SYNCER classes.
1170 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1171 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 3, _( "End Point" ), false, m_boundCtrls );
1172
1173 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1174 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1175
1176 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 0, _( "Center" ), false, m_boundCtrls );
1177 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1178
1179 m_geomSync = std::make_unique<RECTANGLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1180
1181 showPage( *m_gbsRectangleByCorners, true );
1182 showPage( *m_gbsRectangleByCornerSize );
1183 showPage( *m_gbsRectangleByCenterSize );
1184 break;
1185
1186 case SHAPE_T::SEGMENT:
1187
1188 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1189 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 3, _( "End Point" ), false, m_boundCtrls );
1190
1191 AddXYPointToSizer( *aParent, *m_gbsLineByLengthAngle, 0, 0, _( "Start Point" ), false, m_boundCtrls);
1194
1195 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1196 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1197
1198 m_geomSync = std::make_unique<LINE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1199
1200 showPage( *m_gbsLineByEnds, true );
1201 showPage( *m_gbsLineByLengthAngle );
1202 showPage( *m_gbsLineByStartMid );
1203 break;
1204
1205 case SHAPE_T::ARC:
1206 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 0, _( "Center" ), false, m_boundCtrls);
1207 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 3, _( "Start Point" ), false, m_boundCtrls);
1208 AddFieldToSizer( *aParent, *m_gbsArcByCSA, 3, 0, _( "Included Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
1209
1210 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1211 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1212 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 3, 0, _( "End Point" ), false, m_boundCtrls );
1213
1214 AddXYPointToSizer( *aParent, *m_gbsArcByCRAA, 0, 0, _( "Center" ), false, m_boundCtrls );
1215 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 0, 3, _( "Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1216 m_boundCtrls );
1217 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 1, 3, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1218 m_boundCtrls );
1219 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 2, 3, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1220 m_boundCtrls );
1221
1222 m_geomSync = std::make_unique<ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1223
1224 showPage( *m_gbsArcByCSA, true );
1225 showPage( *m_gbsArcBySME );
1226 showPage( *m_gbsArcByCRAA );
1227 break;
1228
1229 case SHAPE_T::CIRCLE:
1230 AddXYPointToSizer( *aParent, *m_gbsCircleCenterRadius, 0, 0, _( "Center" ), false, m_boundCtrls);
1232
1233 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 0, _( "Center" ), false, m_boundCtrls );
1234 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 3, _( "Point on Circle" ), false, m_boundCtrls );
1235
1236 m_geomSync = std::make_unique<CIRCLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1237
1238 showPage( *m_gbsCircleCenterRadius, true );
1239 showPage( *m_gbsCircleCenterPoint );
1240 break;
1241
1242 case SHAPE_T::BEZIER:
1243 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1244 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 3, _( "End Point" ), false, m_boundCtrls );
1245 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 0, _( "Control Point 1" ), false, m_boundCtrls );
1246 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 3, _( "Control Point 2" ), false, m_boundCtrls );
1247
1248 m_geomSync = std::make_unique<BEZIER_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1249
1250 showPage( *m_gbsBezier, TRUE );
1251 break;
1252
1253 case SHAPE_T::POLY:
1254 m_notebookShapeDefs->Hide();
1255 // Nothing to do here...yet
1256 break;
1257
1258 case SHAPE_T::ELLIPSE:
1259 AddXYPointToSizer( *aParent, *m_gbsEllipse, 0, 0, _( "Center" ), false, m_boundCtrls );
1260 AddFieldToSizer( *aParent, *m_gbsEllipse, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1261 m_boundCtrls );
1262 AddFieldToSizer( *aParent, *m_gbsEllipse, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1263 m_boundCtrls );
1264 AddFieldToSizer( *aParent, *m_gbsEllipse, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1265 m_boundCtrls );
1266
1267 m_geomSync = std::make_unique<ELLIPSE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1268
1269 showPage( *m_gbsEllipse, true );
1270 break;
1271
1273 AddXYPointToSizer( *aParent, *m_gbsEllipseArc, 0, 0, _( "Center" ), false, m_boundCtrls );
1274 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1275 m_boundCtrls );
1276 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1277 m_boundCtrls );
1278 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1279 m_boundCtrls );
1280 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 6, 0, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1281 m_boundCtrls );
1282 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 7, 0, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1283 m_boundCtrls );
1284
1285 m_geomSync = std::make_unique<ELLIPSE_ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1286
1287 showPage( *m_gbsEllipseArc, true );
1288 break;
1289
1290 case SHAPE_T::UNDEFINED:
1291 wxFAIL_MSG( "Undefined shape" );
1292 break;
1293 }
1294
1295 // Remove any tabs not used (Hide() doesn't work on Windows)
1296 for( int i = (int) m_notebookShapeDefs->GetPageCount() - 1; i >= 0; --i )
1297 {
1298 if( shownPages.count( i ) == 0 )
1299 m_notebookShapeDefs->RemovePage( i );
1300 }
1301
1302 // Used the last saved tab if any
1303 if( s_lastTabForShape.count( m_item->GetShape() ) > 0
1304 && s_lastTabForShape[m_item->GetShape()] < (int) m_notebookShapeDefs->GetPageCount()
1305 && s_lastTabForShape[m_item->GetShape()] >= 0 )
1306 {
1307 m_notebookShapeDefs->SetSelection( s_lastTabForShape[m_item->GetShape()] );
1308 }
1309
1310 // Find the first control in the shown tab
1311 wxWindow* tabPanel = m_notebookShapeDefs->GetCurrentPage();
1312
1313 for( size_t i = 0; i < m_boundCtrls.size(); ++i )
1314 {
1315 if( m_boundCtrls[i].m_Ctrl->IsDescendant( tabPanel ) )
1316 {
1317 m_boundCtrls[i].m_Ctrl->SetFocus();
1318 break;
1319 }
1320 }
1321
1322 // Do not allow locking items in the footprint editor
1323 m_locked->Show( dynamic_cast<PCB_EDIT_FRAME*>( aParent ) != nullptr );
1324
1325 // Configure the layers list selector
1326 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1327 {
1328 // In the footprint editor, turn off the layers that the footprint doesn't have
1329 const LSET& brdLayers = aParent->GetBoard()->GetEnabledLayers();
1330 LSET forbiddenLayers = LSET::AllLayersMask() & ~brdLayers;
1331
1332 m_LayerSelectionCtrl->SetNotAllowedLayerSet( forbiddenLayers );
1333 }
1334
1335 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
1336 m_lineStyleCombo->Append( lineStyleDesc.name, KiBitmapBundle( lineStyleDesc.bitmap ) );
1337
1338 m_LayerSelectionCtrl->SetLayersHotkeys( false );
1339 m_LayerSelectionCtrl->SetBoardFrame( m_parent );
1340 m_LayerSelectionCtrl->Resync();
1341
1342 m_netSelector->SetNetInfo( &aParent->GetBoard()->GetNetInfo() );
1343
1344 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1345 {
1346 m_netLabel->Hide();
1347 m_netSelector->Hide();
1348 }
1349
1350 if( m_item->GetShape() == SHAPE_T::ARC || m_item->GetShape() == SHAPE_T::SEGMENT
1351 || m_item->GetShape() == SHAPE_T::BEZIER || m_item->GetShape() == SHAPE_T::ELLIPSE_ARC )
1352 {
1353 m_fillLabel->Show( false );
1354 m_fillCtrl->Show( false );
1355 }
1356
1357 if( m_item->GetShape() != SHAPE_T::RECTANGLE )
1358 {
1359 m_cbRoundRect->Show( false );
1360 m_cornerRadius.Show( false );
1361 }
1362
1363 // Only show line ending controls for open shapes
1364 m_endingsSizer->Show( !m_item->IsClosed() );
1365
1366 m_endingsHelpLabel->SetFont( KIUI::GetSmallInfoFont( this ).Italic() );
1367 m_endingsHelpLabel->SetLabel( wxString::Format( _( "Set line ending sizes to 0 for automatic (%g\u00d7 line width)." ),
1369
1371
1372 // Now all widgets have the size fixed, call FinishDialogSettings
1374}
1375
1376
1378{
1379 wxCHECK_RET( aShape, wxT( "ShowGraphicItemPropertiesDialog() error: NULL item" ) );
1380
1381 DIALOG_SHAPE_PROPERTIES dlg( this, aShape );
1382
1383 if( dlg.ShowQuasiModal() == wxID_OK )
1384 {
1385 if( aShape->IsOnLayer( GetActiveLayer() ) )
1386 {
1387 DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
1388 drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
1389 }
1390 }
1391}
1392
1393
1395{
1396 if( !m_cbRoundRect->GetValue() )
1397 m_cornerRadius.ChangeValue( wxEmptyString );
1398}
1399
1400
1401void DIALOG_SHAPE_PROPERTIES::onCornerRadius( wxCommandEvent &event )
1402{
1403 m_cbRoundRect->SetValue( true );
1404}
1405
1406
1408{
1409 if( m_LayerSelectionCtrl->GetLayerSelection() >= 0 )
1410 enableNetInfo();
1411
1413}
1414
1415
1417{
1419}
1420
1421
1423{
1424 if( !m_item )
1425 return false;
1426
1427 // Not all shapes have a syncer (e.g. polygons)
1428 if( m_geomSync )
1430
1431 m_fillCtrl->SetSelection( m_item->GetFillModeProp() );
1432 m_locked->SetValue( m_item->IsLocked() );
1433
1435 {
1436 if( m_item->GetCornerRadius() > 0 )
1437 {
1438 m_cbRoundRect->SetValue( true );
1440 }
1441 else
1442 {
1443 m_cbRoundRect->SetValue( false );
1444 m_cornerRadius.ChangeValue( wxEmptyString );
1445 }
1446 }
1447
1448 m_thickness.SetValue( m_item->GetStroke().GetWidth() );
1449
1450 int style = static_cast<int>( m_item->GetStroke().GetLineStyle() );
1451
1452 if( style >= 0 && style < (int) lineTypeNames.size() )
1453 m_lineStyleCombo->SetSelection( style );
1454 else
1455 m_lineStyleCombo->SetSelection( 0 );
1456
1457 m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
1458
1459 m_hasSolderMask->SetValue( m_item->HasSolderMask() );
1460
1461 if( m_item->GetLocalSolderMaskMargin().has_value() )
1462 m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() );
1463 else
1464 m_solderMaskMargin.SetValue( wxEmptyString );
1465
1466 if( m_parent->GetFrameType() == FRAME_PCB_EDITOR )
1467 {
1468 int net = m_item->GetNetCode();
1469
1470 if( net >= 0 )
1471 {
1472 m_netSelector->SetSelectedNetcode( net );
1473 }
1474 else
1475 {
1476 m_netSelector->SetIndeterminateString( INDETERMINATE_STATE );
1477 m_netSelector->SetIndeterminate();
1478 }
1479 }
1480
1481 enableNetInfo();
1483
1484 // Line endings (only for open shapes)
1485 if( !m_item->IsClosed() )
1486 {
1487 m_startShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetStartEndingStyle() ) );
1488 m_endShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetEndEndingStyle() ) );
1489 m_startLength->SetValue( m_item->GetStartEndingLength() );
1490 m_startWidth->SetValue( m_item->GetStartEndingWidth() );
1491 m_startThickness->SetValue( m_item->GetStartEndingStrokeWidth() );
1492 m_endLength->SetValue( m_item->GetEndEndingLength() );
1493 m_endWidth->SetValue( m_item->GetEndEndingWidth() );
1494 m_endThickness->SetValue( m_item->GetEndEndingStrokeWidth() );
1495 }
1496
1497 return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow();
1498}
1499
1500
1502{
1503 if( !DIALOG_SHAPE_PROPERTIES_BASE::TransferDataFromWindow() )
1504 return false;
1505
1506 if( !m_item )
1507 return true;
1508
1509 int layer = m_LayerSelectionCtrl->GetLayerSelection();
1510
1511 BOARD_COMMIT commit( m_parent );
1512 commit.Modify( m_item );
1513
1514 bool pushCommit = ( m_item->GetEditFlags() == 0 );
1515
1516 // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
1517 // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
1518 if( !pushCommit )
1519 m_item->SetFlags( IN_EDIT );
1520
1522
1523 bool wasLocked = m_item->IsLocked();
1524
1525 if( m_item->GetShape() == SHAPE_T::RECTANGLE )
1526 m_item->SetCornerRadius( m_cbRoundRect->GetValue() ? m_cornerRadius.GetIntValue() : 0 );
1527
1528 m_item->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() );
1529 m_item->SetLocked( m_locked->GetValue() );
1530
1531 m_item->SetWidth( m_thickness.GetIntValue() );
1532
1533 auto it = lineTypeNames.begin();
1534 std::advance( it, m_lineStyleCombo->GetSelection() );
1535
1536 if( it == lineTypeNames.end() )
1537 m_item->SetLineStyle( LINE_STYLE::SOLID );
1538 else
1539 m_item->SetLineStyle( it->first );
1540
1541 m_item->SetLayer( ToLAYER_ID( layer ) );
1542
1543 m_item->SetHasSolderMask( m_hasSolderMask->GetValue() );
1544
1545 if( m_solderMaskMargin.IsNull() )
1546 m_item->SetLocalSolderMaskMargin( {} );
1547 else
1548 m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() );
1549
1550 // Line endings (only for open shapes)
1551 if( !m_item->IsClosed() )
1552 {
1553 int startSel = m_startShapeChoice->GetSelection();
1554
1555 if( startSel >= 0 && startSel < LINE_ENDING::s_defaultChoiceCount )
1556 m_item->SetStartEndingStyle( LINE_ENDING::s_defaultChoiceOrder[startSel] );
1557
1558 int endSel = m_endShapeChoice->GetSelection();
1559
1560 if( endSel >= 0 && endSel < LINE_ENDING::s_defaultChoiceCount )
1561 m_item->SetEndEndingStyle( LINE_ENDING::s_defaultChoiceOrder[endSel] );
1562
1563 if( !m_startLength->IsIndeterminate() )
1564 m_item->SetStartEndingLength( std::max( 0, m_startLength->GetIntValue() ) );
1565
1566 if( !m_startWidth->IsIndeterminate() )
1567 m_item->SetStartEndingWidth( std::max( 0, m_startWidth->GetIntValue() ) );
1568
1569 if( !m_startThickness->IsIndeterminate() )
1570 m_item->SetStartEndingStrokeWidth( std::max( 0, m_startThickness->GetIntValue() ) );
1571
1572 if( !m_endLength->IsIndeterminate() )
1573 m_item->SetEndEndingLength( std::max( 0, m_endLength->GetIntValue() ) );
1574
1575 if( !m_endWidth->IsIndeterminate() )
1576 m_item->SetEndEndingWidth( std::max( 0, m_endWidth->GetIntValue() ) );
1577
1578 if( !m_endThickness->IsIndeterminate() )
1579 m_item->SetEndEndingStrokeWidth( std::max( 0, m_endThickness->GetIntValue() ) );
1580 }
1581
1582 m_item->RebuildBezierToSegmentsPointsList( m_item->GetMaxError() );
1583
1584 if( m_item->IsOnCopperLayer() )
1585 m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
1586 else
1587 m_item->SetNetCode( -1 );
1588
1589 if( pushCommit )
1590 commit.Push( _( "Edit Shape Properties" ) );
1591
1592 // Save the tab
1593 s_lastTabForShape[m_item->GetShape()] = m_notebookShapeDefs->GetSelection();
1594
1595 // Notify clients which treat locked and unlocked items differently (ie: POINT_EDITOR)
1596 if( wasLocked != m_item->IsLocked() )
1597 m_parent->GetToolManager()->PostEvent( EVENTS::SelectedEvent );
1598
1599 return true;
1600}
1601
1602
1604{
1605 wxArrayString errors;
1606
1607 if( !DIALOG_SHAPE_PROPERTIES_BASE::Validate() )
1608 return false;
1609
1610 if( m_geomSync )
1611 m_geomSync->Validate( errors );
1612
1613 // Type specific checks.
1614 switch( m_item->GetShape() )
1615 {
1616 case SHAPE_T::ARC:
1617 if( m_thickness.GetValue() <= 0 )
1618 errors.Add( _( "Line width must be greater than zero." ) );
1619 break;
1620
1621 case SHAPE_T::CIRCLE:
1622 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1623 errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) );
1624
1625 break;
1626
1627 case SHAPE_T::RECTANGLE:
1628 {
1629 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1630 errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) );
1631
1632 const RECTANGLE_GEOM_SYNCER* rectGeomSync = static_cast<RECTANGLE_GEOM_SYNCER*>( m_geomSync.get() );
1633 int shortSide = std::min( rectGeomSync->GetRectangleWidth(), rectGeomSync->GetRectangleHeight() );
1634
1635 if( m_cbRoundRect->GetValue() && m_cornerRadius.GetIntValue() * 2 > shortSide )
1636 {
1637 errors.Add( _( "Corner radius must be less than or equal to half the smaller side." ) );
1638 m_cornerRadius.SetValue( KiROUND( shortSide / 2.0 ) );
1639 }
1640
1641 break;
1642 }
1643
1644 case SHAPE_T::POLY:
1645 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1646 errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) );
1647
1648 break;
1649
1650 case SHAPE_T::SEGMENT:
1651 if( m_thickness.GetValue() <= 0 )
1652 errors.Add( _( "Line width must be greater than zero." ) );
1653
1654 break;
1655
1656 case SHAPE_T::BEZIER:
1657 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1658 errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) );
1659
1660 break;
1661
1662 case SHAPE_T::ELLIPSE:
1663 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1664 errors.Add( _( "Line width must be greater than zero for an unfilled ellipse." ) );
1665
1666 break;
1667
1669 if( m_thickness.GetValue() <= 0 )
1670 errors.Add( _( "Line width must be greater than zero." ) );
1671
1672 break;
1673
1674 default:
1675 UNIMPLEMENTED_FOR( m_item->SHAPE_T_asString() );
1676 break;
1677 }
1678
1679 if( errors.GetCount() )
1680 {
1681 HTML_MESSAGE_BOX dlg( this, _( "Error List" ) );
1682 dlg.ListSet( errors );
1683 dlg.ShowModal();
1684 }
1685
1686 return errors.GetCount() == 0;
1687}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
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:1217
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
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_startLength
std::unique_ptr< UNIT_BINDER > m_endLength
std::unique_ptr< UNIT_BINDER > m_startThickness
bool TransferDataToWindow() override
std::unique_ptr< UNIT_BINDER > m_endThickness
void onCornerRadius(wxCommandEvent &event) override
~DIALOG_SHAPE_PROPERTIES() override=default
void onRoundedRectChanged(wxCommandEvent &event) override
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:115
UI_FILL_MODE GetFillModeProp() const
int GetEllipseMinorRadius() const
Definition eda_shape.h:403
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:376
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:385
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:431
int GetEllipseMajorRadius() const
Definition eda_shape.h:394
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:412
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:177
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
void SetRadius(int aX)
Definition eda_shape.h:358
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:422
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:373
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:346
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:40
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 SetCenter(const VECTOR2I &aCenter) 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:80
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Definition pcb_shape.h:109
void SetEllipseStartAngle(const EDA_ANGLE &aA) override
void SetEllipseEndAngle(const EDA_ANGLE &aA) override
void SetEnd(const VECTOR2I &aEnd) override
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 SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd) override
Set the three controlling points for an arc.
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:574
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
UI_FILL_MODE
Definition eda_fill.h:41
@ SOLID
Definition eda_fill.h:43
#define IN_EDIT
Item currently edited.
@ 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)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:741
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:752
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, 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:437
static std::map< SHAPE_T, int > s_lastTabForShape
static bool isCopper(const PNS::ITEM *aItem)
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
static double sweep(float aStart, float aEnd)
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
bool IsStraightArc(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Definition trigo.cpp:549
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:540
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:47
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707