KiCad PCB EDA Suite
Loading...
Searching...
No Matches
margin_offset_binder.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <wx/stattext.h>
23#include <wx/textentry.h>
24#include <wx/regex.h>
25#include <eda_units.h>
26#include <eda_draw_frame.h>
27
28using namespace EDA_UNIT_UTILS::UI;
29
30
31MARGIN_OFFSET_BINDER::MARGIN_OFFSET_BINDER( EDA_DRAW_FRAME* aParent, wxStaticText* aLabel, wxWindow* aValueCtrl,
32 wxStaticText* aUnitLabel ) :
33 MARGIN_OFFSET_BINDER( aParent, aParent, aLabel, aValueCtrl, aUnitLabel )
34{
35}
36
37
38MARGIN_OFFSET_BINDER::MARGIN_OFFSET_BINDER( UNITS_PROVIDER* aUnitsProvider, wxWindow* aEventSource,
39 wxStaticText* aLabel, wxWindow* aValueCtrl, wxStaticText* aUnitLabel ) :
40 m_label( aLabel ),
41 m_valueCtrl( aValueCtrl ),
42 m_eventSource( aEventSource ),
43 m_unitLabel( aUnitLabel ),
44 m_iuScale( &aUnitsProvider->GetIuScale() ),
45 m_units( aUnitsProvider->GetUserUnits() ),
46 m_eval( aUnitsProvider->GetUserUnits() ),
47 m_cachedOffset( std::nullopt ),
48 m_cachedRatio( std::nullopt ),
49 m_needsParsing( true )
50{
51 if( m_valueCtrl )
52 {
53 m_valueCtrl->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( MARGIN_OFFSET_BINDER::onSetFocus ),
54 nullptr, this );
55 m_valueCtrl->Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( MARGIN_OFFSET_BINDER::onKillFocus ),
56 nullptr, this );
57 m_valueCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( MARGIN_OFFSET_BINDER::onTextChanged ),
58 nullptr, this );
59 }
60
61 if( m_eventSource )
62 {
63 m_eventSource->Connect( EDA_EVT_UNITS_CHANGED, wxCommandEventHandler( MARGIN_OFFSET_BINDER::onUnitsChanged ),
64 nullptr, this );
65 }
66
67 // Update the unit label to indicate combined format
68 if( m_unitLabel )
69 m_unitLabel->SetLabel( wxEmptyString );
70}
71
72
74{
75 if( m_valueCtrl )
76 {
77 m_valueCtrl->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( MARGIN_OFFSET_BINDER::onSetFocus ),
78 nullptr, this );
79 m_valueCtrl->Disconnect( wxEVT_KILL_FOCUS, wxFocusEventHandler( MARGIN_OFFSET_BINDER::onKillFocus ),
80 nullptr, this );
81 m_valueCtrl->Disconnect( wxEVT_TEXT, wxCommandEventHandler( MARGIN_OFFSET_BINDER::onTextChanged ),
82 nullptr, this );
83 }
84
85 if( m_eventSource )
86 {
87 m_eventSource->Disconnect( EDA_EVT_UNITS_CHANGED, wxCommandEventHandler( MARGIN_OFFSET_BINDER::onUnitsChanged ),
88 nullptr, this );
89 }
90}
91
92
93void MARGIN_OFFSET_BINDER::SetOffsetValue( std::optional<int> aValue )
94{
95 m_cachedOffset = aValue;
96 m_needsParsing = false;
98}
99
100
101void MARGIN_OFFSET_BINDER::SetRatioValue( std::optional<double> aRatio )
102{
103 m_cachedRatio = aRatio;
104 m_needsParsing = false;
106}
107
108
109std::optional<int> MARGIN_OFFSET_BINDER::GetOffsetValue() const
110{
111 if( m_needsParsing )
112 {
113 wxString input = getTextValue();
115 m_needsParsing = false;
116 }
117
118 return m_cachedOffset;
119}
120
121
122std::optional<double> MARGIN_OFFSET_BINDER::GetRatioValue() const
123{
124 if( m_needsParsing )
125 {
126 wxString input = getTextValue();
128 m_needsParsing = false;
129 }
130
131 return m_cachedRatio;
132}
133
134
136{
137 wxString text = getTextValue();
138 text.Trim( true ).Trim( false );
139 return text.IsEmpty();
140}
141
142
144{
145 if( m_label )
146 m_label->Enable( aEnable );
147
148 if( m_valueCtrl )
149 m_valueCtrl->Enable( aEnable );
150
151 if( m_unitLabel )
152 m_unitLabel->Enable( aEnable );
153}
154
155
156void MARGIN_OFFSET_BINDER::Show( bool aShow, bool aResize )
157{
158 if( m_label )
159 {
160 m_label->Show( aShow );
161
162 if( aResize )
163 {
164 if( aShow )
165 m_label->SetSize( -1, -1 );
166 else
167 m_label->SetSize( 0, 0 );
168 }
169 }
170
171 if( m_valueCtrl )
172 {
173 m_valueCtrl->Show( aShow );
174
175 if( aResize )
176 {
177 if( aShow )
178 m_valueCtrl->SetSize( -1, -1 );
179 else
180 m_valueCtrl->SetSize( 0, 0 );
181 }
182 }
183
184 if( m_unitLabel )
185 {
186 m_unitLabel->Show( aShow );
187
188 if( aResize )
189 {
190 if( aShow )
191 m_unitLabel->SetSize( -1, -1 );
192 else
193 m_unitLabel->SetSize( 0, 0 );
194 }
195 }
196}
197
198
199void MARGIN_OFFSET_BINDER::onSetFocus( wxFocusEvent& aEvent )
200{
201 m_needsParsing = true;
202 aEvent.Skip();
203}
204
205
206void MARGIN_OFFSET_BINDER::onTextChanged( wxCommandEvent& aEvent )
207{
208 // GetOffsetValue/GetRatioValue clear m_needsParsing after each parse, so any subsequent
209 // text edit within the same focus session must re-arm it.
210 m_needsParsing = true;
211 aEvent.Skip();
212}
213
214
215static wxString valueDescriptionFromLabel( wxStaticText* aLabel )
216{
217 wxString desc = aLabel->GetLabel();
218
219 desc.EndsWith( wxT( ":" ), &desc );
220 return desc;
221}
222
223
225{
226 std::optional<int> offset;
227 std::optional<double> ratio;
228
229 if( !parseInput( getTextValue(), offset, ratio ) )
230 {
231 return wxString::Format( _( "%s could not be parsed as a distance and/or percentage offset." ),
233 }
234
235 return wxEmptyString;
236}
237
238
239void MARGIN_OFFSET_BINDER::onKillFocus( wxFocusEvent& aEvent )
240{
241 // Parse and reformat the value on focus loss
242 wxString input = getTextValue();
243 std::optional<int> offset;
244 std::optional<double> ratio;
245
246 if( parseInput( input, offset, ratio ) )
247 {
248 m_cachedOffset = offset;
249 m_cachedRatio = ratio;
250 m_needsParsing = false;
252 }
253
254 aEvent.Skip();
255}
256
257
258void MARGIN_OFFSET_BINDER::onUnitsChanged( wxCommandEvent& aEvent )
259{
260 if( EDA_BASE_FRAME* provider = static_cast<EDA_BASE_FRAME*>( aEvent.GetClientData() ) )
261 {
262 // Preserve current values
263 std::optional<int> offset = GetOffsetValue();
264 std::optional<double> ratio = GetRatioValue();
265
266 // Update units
267 m_units = provider->GetUserUnits();
268 m_iuScale = &provider->GetIuScale();
269 m_eval.SetDefaultUnits( m_units );
270
271 // Reformat with new units
272 setTextValue( formatValue( offset, ratio ) );
273 }
274
275 aEvent.Skip();
276}
277
278
279bool MARGIN_OFFSET_BINDER::parseInput( const wxString& aInput, std::optional<int>& aOffset,
280 std::optional<double>& aRatio ) const
281{
282 aOffset = std::nullopt;
283 aRatio = std::nullopt;
284
285 wxString input = aInput;
286 input.Trim( true ).Trim( false );
287
288 if( input.IsEmpty() )
289 return true;
290
291 // Strategy: find all terms separated by + and - operators
292 // Each term is either a distance value (with optional unit) or a percentage
293
294 // First, normalize the input by adding spaces around operators
295 wxString normalized;
296 bool lastWasOperator = true; // Start as true to handle leading negative
297
298 for( size_t i = 0; i < input.Length(); ++i )
299 {
300 wxChar ch = input[i];
301
302 if( ( ch == '+' || ch == '-' ) && !lastWasOperator )
303 {
304 // This is an operator between terms
305 normalized += ' ';
306 normalized += ch;
307 normalized += ' ';
308 lastWasOperator = true;
309 }
310 else
311 {
312 normalized += ch;
313 lastWasOperator = ( ch == '+' || ch == '-' );
314 }
315 }
316
317 // Split into terms
318 wxArrayString terms;
319 wxString currentTerm;
320
321 for( size_t i = 0; i < normalized.Length(); ++i )
322 {
323 wxChar ch = normalized[i];
324
325 if( ch == ' ' )
326 {
327 if( !currentTerm.IsEmpty() )
328 {
329 currentTerm.Trim( true ).Trim( false );
330 terms.Add( currentTerm );
331 currentTerm.Clear();
332 }
333 }
334 else
335 {
336 currentTerm += ch;
337 }
338 }
339
340 if( !currentTerm.IsEmpty() )
341 {
342 currentTerm.Trim( true ).Trim( false );
343 terms.Add( currentTerm );
344 }
345
346 // Process each term
347 double totalOffset = 0.0;
348 double totalRatio = 0.0;
349 bool hasOffset = false;
350 bool hasRatio = false;
351 double sign = 1.0;
352
353 for( const wxString& term : terms )
354 {
355 if( term == "+" )
356 {
357 sign = 1.0;
358 continue;
359 }
360 else if( term == "-" )
361 {
362 sign = -1.0;
363 continue;
364 }
365
366 bool invalidTerm = true;
367
368 // Check if this term is a percentage
369 if( term.EndsWith( "%" ) )
370 {
371 wxString numPart = term.Left( term.Length() - 1 );
372 numPart.Trim( true ).Trim( false );
373 double value;
374
375 if( numPart.ToDouble( &value ) )
376 {
377 totalRatio += sign * value / 100.0;
378 hasRatio = true;
379 invalidTerm = false;
380 }
381 }
382 else
383 {
384 // Try to parse as a distance value using the evaluator
386
387 if( eval.Process( term ) )
388 {
389 wxString result = eval.Result();
390 double value;
391
392 if( result.ToDouble( &value ) )
393 {
394 // Convert from user units to internal units
396 totalOffset += sign * iuValue;
397 hasOffset = true;
398 invalidTerm = false;
399 }
400 }
401 }
402
403 if( invalidTerm )
404 return false;
405
406 sign = 1.0; // Reset sign for next term
407 }
408
409 if( hasOffset )
410 aOffset = static_cast<int>( totalOffset );
411
412 if( hasRatio )
413 aRatio = totalRatio;
414
415 return hasOffset || hasRatio;
416}
417
418
419wxString MARGIN_OFFSET_BINDER::formatValue( std::optional<int> aOffset, std::optional<double> aRatio ) const
420{
421 wxString result;
422
423 // Format offset value
424 if( aOffset.has_value() && aOffset.value() != 0 )
425 {
427 }
428
429 // Format ratio value
430 if( aRatio.has_value() && std::abs( aRatio.value() ) > 1e-9 )
431 {
432 double percent = aRatio.value() * 100.0;
433
434 if( !result.IsEmpty() )
435 {
436 // Add the ratio with appropriate sign
437 if( percent >= 0 )
438 result += wxString::Format( " + %.4g%%", percent );
439 else
440 result += wxString::Format( " - %.4g%%", -percent );
441 }
442 else
443 {
444 result = wxString::Format( "%.4g%%", percent );
445 }
446 }
447 else if( aOffset.has_value() && aOffset.value() == 0 )
448 {
449 // No value formats as empty string; a value of 0 must format as "0"
450 result = wxT( "0" );
451 }
452
453 return result;
454}
455
456
458{
459 wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( m_valueCtrl );
460
461 if( textEntry )
462 return textEntry->GetValue();
463
464 return wxEmptyString;
465}
466
467
468void MARGIN_OFFSET_BINDER::setTextValue( const wxString& aValue )
469{
470 wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( m_valueCtrl );
471
472 if( textEntry )
473 textEntry->ChangeValue( aValue );
474}
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
The base frame for deriving all KiCad main window classes.
The base class for create windows for drawing purpose.
bool IsNull() const
Return true if the control holds no value (ie: empty string).
std::optional< int > GetOffsetValue() const
Get the absolute offset value (in Internal Units).
void onTextChanged(wxCommandEvent &aEvent)
MARGIN_OFFSET_BINDER(EDA_DRAW_FRAME *aParent, wxStaticText *aLabel, wxWindow *aValueCtrl, wxStaticText *aUnitLabel)
std::optional< double > GetRatioValue() const
Get the ratio value as a fraction (e.g., -0.05 for -5%).
const EDA_IU_SCALE * m_iuScale
virtual ~MARGIN_OFFSET_BINDER() override
void Enable(bool aEnable)
Enable/disable the label, widget and units label.
void onSetFocus(wxFocusEvent &aEvent)
bool parseInput(const wxString &aInput, std::optional< int > &aOffset, std::optional< double > &aRatio) const
Parse the input string and extract offset and ratio values.
wxStaticText * m_unitLabel
Can be nullptr.
void SetRatioValue(std::optional< double > aRatio)
Set the ratio value as a fraction (e.g., -0.05 for -5%).
std::optional< int > m_cachedOffset
void Show(bool aShow, bool aResize=false)
Show/hide the label, widget and units label.
std::optional< double > m_cachedRatio
wxString getTextValue() const
Get the current text from the control.
void onKillFocus(wxFocusEvent &aEvent)
void setTextValue(const wxString &aValue)
Set the text in the control without triggering events.
wxString formatValue(std::optional< int > aOffset, std::optional< double > aRatio) const
Format the offset and ratio values into a display string.
void onUnitsChanged(wxCommandEvent &aEvent)
void SetOffsetValue(std::optional< int > aValue)
Set the absolute offset value (in Internal Units).
wxString Result() const
bool Process(const wxString &aString)
#define _(s)
static wxString valueDescriptionFromLabel(wxStaticText *aLabel)
KICOMMON_API long long int ValueFromString(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Convert aTextValue in aUnits to internal units used by the application.
KICOMMON_API wxString StringFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Return the string from aValue according to aUnits (inch, mm ...) for display.
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
wxString result
Test unit parsing edge cases and error handling.
constexpr int sign(T val)
Definition util.h:167