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