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
32 wxWindow* aValueCtrl, wxStaticText* aUnitLabel ) :
33 MARGIN_OFFSET_BINDER( aParent, aParent, aLabel, aValueCtrl, aUnitLabel )
34{
35}
36
37
39 wxWindow* aEventSource, wxStaticText* aLabel,
40 wxWindow* aValueCtrl, wxStaticText* aUnitLabel ) :
41 m_label( aLabel ),
42 m_valueCtrl( aValueCtrl ),
43 m_eventSource( aEventSource ),
44 m_unitLabel( aUnitLabel ),
45 m_iuScale( &aUnitsProvider->GetIuScale() ),
46 m_units( aUnitsProvider->GetUserUnits() ),
47 m_eval( aUnitsProvider->GetUserUnits() ),
48 m_cachedOffset( std::nullopt ),
49 m_cachedRatio( std::nullopt ),
50 m_needsParsing( true )
51{
52 if( m_valueCtrl )
53 {
54 m_valueCtrl->Connect( wxEVT_SET_FOCUS,
55 wxFocusEventHandler( MARGIN_OFFSET_BINDER::onSetFocus ),
56 nullptr, this );
57 m_valueCtrl->Connect( wxEVT_KILL_FOCUS,
58 wxFocusEventHandler( MARGIN_OFFSET_BINDER::onKillFocus ),
59 nullptr, this );
60 m_valueCtrl->Connect( wxEVT_TEXT,
61 wxCommandEventHandler( MARGIN_OFFSET_BINDER::onTextChanged ),
62 nullptr, this );
63 }
64
65 if( m_eventSource )
66 {
67 m_eventSource->Connect( EDA_EVT_UNITS_CHANGED,
68 wxCommandEventHandler( MARGIN_OFFSET_BINDER::onUnitsChanged ),
69 nullptr, this );
70 }
71
72 // Update the unit label to indicate combined format
73 if( m_unitLabel )
74 m_unitLabel->SetLabel( wxEmptyString );
75}
76
77
79{
80 if( m_valueCtrl )
81 {
82 m_valueCtrl->Disconnect( wxEVT_SET_FOCUS,
83 wxFocusEventHandler( MARGIN_OFFSET_BINDER::onSetFocus ),
84 nullptr, this );
85 m_valueCtrl->Disconnect( wxEVT_KILL_FOCUS,
86 wxFocusEventHandler( MARGIN_OFFSET_BINDER::onKillFocus ),
87 nullptr, this );
88 m_valueCtrl->Disconnect( wxEVT_TEXT,
89 wxCommandEventHandler( MARGIN_OFFSET_BINDER::onTextChanged ),
90 nullptr, this );
91 }
92
93 if( m_eventSource )
94 {
95 m_eventSource->Disconnect( EDA_EVT_UNITS_CHANGED,
96 wxCommandEventHandler( MARGIN_OFFSET_BINDER::onUnitsChanged ),
97 nullptr, this );
98 }
99}
100
101
102void MARGIN_OFFSET_BINDER::SetOffsetValue( std::optional<int> aValue )
103{
104 m_cachedOffset = aValue;
105 m_needsParsing = false;
107}
108
109
110void MARGIN_OFFSET_BINDER::SetRatioValue( std::optional<double> aRatio )
111{
112 m_cachedRatio = aRatio;
113 m_needsParsing = false;
115}
116
117
118std::optional<int> MARGIN_OFFSET_BINDER::GetOffsetValue() const
119{
120 if( m_needsParsing )
121 {
122 wxString input = getTextValue();
124 m_needsParsing = false;
125 }
126
127 return m_cachedOffset;
128}
129
130
131std::optional<double> MARGIN_OFFSET_BINDER::GetRatioValue() const
132{
133 if( m_needsParsing )
134 {
135 wxString input = getTextValue();
137 m_needsParsing = false;
138 }
139
140 return m_cachedRatio;
141}
142
143
145{
146 wxString text = getTextValue();
147 text.Trim( true ).Trim( false );
148 return text.IsEmpty();
149}
150
151
153{
154 if( m_label )
155 m_label->Enable( aEnable );
156
157 if( m_valueCtrl )
158 m_valueCtrl->Enable( aEnable );
159
160 if( m_unitLabel )
161 m_unitLabel->Enable( aEnable );
162}
163
164
165void MARGIN_OFFSET_BINDER::Show( bool aShow, bool aResize )
166{
167 if( m_label )
168 {
169 m_label->Show( aShow );
170
171 if( aResize )
172 {
173 if( aShow )
174 m_label->SetSize( -1, -1 );
175 else
176 m_label->SetSize( 0, 0 );
177 }
178 }
179
180 if( m_valueCtrl )
181 {
182 m_valueCtrl->Show( aShow );
183
184 if( aResize )
185 {
186 if( aShow )
187 m_valueCtrl->SetSize( -1, -1 );
188 else
189 m_valueCtrl->SetSize( 0, 0 );
190 }
191 }
192
193 if( m_unitLabel )
194 {
195 m_unitLabel->Show( aShow );
196
197 if( aResize )
198 {
199 if( aShow )
200 m_unitLabel->SetSize( -1, -1 );
201 else
202 m_unitLabel->SetSize( 0, 0 );
203 }
204 }
205}
206
207
208void MARGIN_OFFSET_BINDER::onSetFocus( wxFocusEvent& aEvent )
209{
210 m_needsParsing = true;
211 aEvent.Skip();
212}
213
214
215void MARGIN_OFFSET_BINDER::onTextChanged( wxCommandEvent& aEvent )
216{
217 // GetOffsetValue/GetRatioValue clear m_needsParsing after each parse, so any subsequent
218 // text edit within the same focus session must re-arm it.
219 m_needsParsing = true;
220 aEvent.Skip();
221}
222
223
224void MARGIN_OFFSET_BINDER::onKillFocus( wxFocusEvent& aEvent )
225{
226 // Parse and reformat the value on focus loss
227 wxString input = getTextValue();
228 std::optional<int> offset;
229 std::optional<double> ratio;
230
231 if( parseInput( input, offset, ratio ) )
232 {
233 m_cachedOffset = offset;
234 m_cachedRatio = ratio;
235 m_needsParsing = false;
237 }
238
239 aEvent.Skip();
240}
241
242
243void MARGIN_OFFSET_BINDER::onUnitsChanged( wxCommandEvent& aEvent )
244{
245 EDA_BASE_FRAME* provider = static_cast<EDA_BASE_FRAME*>( aEvent.GetClientData() );
246
247 // Preserve current values
248 std::optional<int> offset = GetOffsetValue();
249 std::optional<double> ratio = GetRatioValue();
250
251 // Update units
252 m_units = provider->GetUserUnits();
253 m_iuScale = &provider->GetIuScale();
254 m_eval.SetDefaultUnits( m_units );
255
256 // Reformat with new units
257 setTextValue( formatValue( offset, ratio ) );
258
259 aEvent.Skip();
260}
261
262
263bool MARGIN_OFFSET_BINDER::parseInput( const wxString& aInput, std::optional<int>& aOffset,
264 std::optional<double>& aRatio ) const
265{
266 aOffset = std::nullopt;
267 aRatio = std::nullopt;
268
269 wxString input = aInput;
270 input.Trim( true ).Trim( false );
271
272 if( input.IsEmpty() )
273 return true;
274
275 // Strategy: find all terms separated by + and - operators
276 // Each term is either a distance value (with optional unit) or a percentage
277
278 // First, normalize the input by adding spaces around operators
279 wxString normalized;
280 bool lastWasOperator = true; // Start as true to handle leading negative
281
282 for( size_t i = 0; i < input.Length(); ++i )
283 {
284 wxChar ch = input[i];
285
286 if( ( ch == '+' || ch == '-' ) && !lastWasOperator )
287 {
288 // This is an operator between terms
289 normalized += ' ';
290 normalized += ch;
291 normalized += ' ';
292 lastWasOperator = true;
293 }
294 else
295 {
296 normalized += ch;
297 lastWasOperator = ( ch == '+' || ch == '-' );
298 }
299 }
300
301 // Split into terms
302 wxArrayString terms;
303 wxString currentTerm;
304
305 for( size_t i = 0; i < normalized.Length(); ++i )
306 {
307 wxChar ch = normalized[i];
308
309 if( ch == ' ' )
310 {
311 if( !currentTerm.IsEmpty() )
312 {
313 currentTerm.Trim( true ).Trim( false );
314 terms.Add( currentTerm );
315 currentTerm.Clear();
316 }
317 }
318 else
319 {
320 currentTerm += ch;
321 }
322 }
323
324 if( !currentTerm.IsEmpty() )
325 {
326 currentTerm.Trim( true ).Trim( false );
327 terms.Add( currentTerm );
328 }
329
330 // Process each term
331 double totalOffset = 0.0;
332 double totalRatio = 0.0;
333 bool hasOffset = false;
334 bool hasRatio = false;
335 double sign = 1.0;
336
337 for( const wxString& term : terms )
338 {
339 if( term == "+" )
340 {
341 sign = 1.0;
342 continue;
343 }
344 else if( term == "-" )
345 {
346 sign = -1.0;
347 continue;
348 }
349
350 // Check if this term is a percentage
351 if( term.EndsWith( "%" ) )
352 {
353 wxString numPart = term.Left( term.Length() - 1 );
354 numPart.Trim( true ).Trim( false );
355 double value;
356
357 if( numPart.ToDouble( &value ) )
358 {
359 totalRatio += sign * value / 100.0;
360 hasRatio = true;
361 }
362 }
363 else
364 {
365 // Try to parse as a distance value using the evaluator
367
368 if( eval.Process( term ) )
369 {
370 wxString result = eval.Result();
371 double value;
372
373 if( result.ToDouble( &value ) )
374 {
375 // Convert from user units to internal units
377 totalOffset += sign * iuValue;
378 hasOffset = true;
379 }
380 }
381 }
382
383 sign = 1.0; // Reset sign for next term
384 }
385
386 if( hasOffset )
387 aOffset = static_cast<int>( totalOffset );
388
389 if( hasRatio )
390 aRatio = totalRatio;
391
392 return hasOffset || hasRatio || input.IsEmpty();
393}
394
395
396wxString MARGIN_OFFSET_BINDER::formatValue( std::optional<int> aOffset,
397 std::optional<double> aRatio ) const
398{
399 wxString result;
400
401 // Format offset value
402 if( aOffset.has_value() && aOffset.value() != 0 )
403 {
405 }
406
407 // Format ratio value
408 if( aRatio.has_value() && std::abs( aRatio.value() ) > 1e-9 )
409 {
410 double percent = aRatio.value() * 100.0;
411
412 if( !result.IsEmpty() )
413 {
414 // Add the ratio with appropriate sign
415 if( percent >= 0 )
416 result += wxString::Format( " + %.4g%%", percent );
417 else
418 result += wxString::Format( " - %.4g%%", -percent );
419 }
420 else
421 {
422 result = wxString::Format( "%.4g%%", percent );
423 }
424 }
425
426 return result;
427}
428
429
431{
432 wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( m_valueCtrl );
433
434 if( textEntry )
435 return textEntry->GetValue();
436
437 return wxEmptyString;
438}
439
440
441void MARGIN_OFFSET_BINDER::setTextValue( const wxString& aValue )
442{
443 wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( m_valueCtrl );
444
445 if( textEntry )
446 textEntry->ChangeValue( aValue );
447}
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)
const EDA_IU_SCALE & GetIuScale() const
EDA_UNITS GetUserUnits() const
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:400
wxString result
Test unit parsing edge cases and error handling.
constexpr int sign(T val)
Definition util.h:141