KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_find_by_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 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 <pcb_edit_frame.h>
23#include <board.h>
24#include <footprint.h>
25#include <pad.h>
26#include <pcb_group.h>
27#include <pcb_track.h>
28#include <pcb_shape.h>
29#include <pcb_text.h>
30#include <zone.h>
31#include <tool/tool_manager.h>
33#include <tools/pcb_actions.h>
34#include <tool/actions.h>
36#include <properties/property.h>
38#include <scintilla_tricks.h>
39#include <pcbexpr_evaluator.h>
41#include <project.h>
43#include <wx/grid.h>
44#include <wx/stc/stc.h>
45#include <wx/msgdlg.h>
46#include <wx/regex.h>
47
48#include <algorithm>
49
50
52 DIALOG_FIND_BY_PROPERTIES_BASE( aParent, wxID_ANY, _( "Find by Properties" ) ),
53 m_frame( aParent ),
54 m_board( nullptr ),
55 m_scintillaTricks( nullptr )
56{
57 m_propertyGrid->SetColLabelValue( 0, _( "Property" ) );
58 m_propertyGrid->SetColLabelValue( 1, _( "Value" ) );
59 m_propertyGrid->SetColLabelValue( 2, _( "Match" ) );
60 m_propertyGrid->SetRowLabelSize( 0 );
61 m_propertyGrid->SetColLabelSize( wxGRID_AUTOSIZE );
62 m_propertyGrid->EnableEditing( true );
63 m_propertyGrid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
64 m_propertyGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
65
67 m_queryEditor, wxT( "()" ), true,
68 [this]( wxKeyEvent& aEvent )
69 {
70 wxCommandEvent dummy;
72 },
73 [this]( wxStyledTextEvent& aEvent )
74 {
75 onScintillaCharAdded( aEvent );
76 } );
77
78 m_queryEditor->SetMarginWidth( 0, 0 );
79 m_queryEditor->SetMarginWidth( 1, 0 );
80 m_queryEditor->SetMarginWidth( 2, 0 );
81
82 m_board = m_frame->GetBoard();
83
84 m_frame->Bind( EDA_EVT_BOARD_CHANGED, &DIALOG_FIND_BY_PROPERTIES::OnBoardChanged, this );
85
86 m_propertyGrid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_FIND_BY_PROPERTIES::onGridCellChanged, this );
87 m_propertyGrid->Bind( wxEVT_GRID_CELL_LEFT_CLICK, &DIALOG_FIND_BY_PROPERTIES::onGridCellClick, this );
89
92
93 SetMinSize( wxSize( 350, 400 ) );
94 m_propertyGrid->SetMinSize( wxSize( 0, 200 ) );
95 m_selectMatchingBtn->SetDefault();
96 Center();
97}
98
99
106
107
109{
110 if( show )
112
114}
115
116
117void DIALOG_FIND_BY_PROPERTIES::OnBoardChanged( wxCommandEvent& event )
118{
119 m_board = m_frame->GetBoard();
120 event.Skip();
121}
122
123
125{
126 if( IsShown() && m_notebook->GetSelection() == 0 )
128}
129
130
131wxVariant DIALOG_FIND_BY_PROPERTIES::anyToVariant( const wxAny& aValue )
132{
133 if( aValue.CheckType<int>() )
134 return wxVariant( aValue.As<int>() );
135 else if( aValue.CheckType<long>() )
136 return wxVariant( aValue.As<long>() );
137 else if( aValue.CheckType<long long>() )
138 // Use double to avoid truncation on platforms where long is 32-bit
139 return wxVariant( static_cast<double>( aValue.As<long long>() ) );
140 else if( aValue.CheckType<double>() )
141 return wxVariant( aValue.As<double>() );
142 else if( aValue.CheckType<bool>() )
143 return wxVariant( aValue.As<bool>() );
144 else if( aValue.CheckType<wxString>() )
145 return wxVariant( aValue.As<wxString>() );
146
147 wxVariant variant;
148
149 if( aValue.GetAs( &variant ) )
150 return variant;
151
152 wxString strVal;
153
154 if( aValue.GetAs( &strVal ) )
155 return wxVariant( strVal );
156
157 return wxVariant();
158}
159
160
162{
163 if( aItem->Type() == PCB_FOOTPRINT_T )
164 {
165 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
166 const wxString& propName = aProperty->Name();
167 wxString variantName;
168
169 if( footprint->GetBoard() )
170 variantName = footprint->GetBoard()->GetCurrentVariant();
171
172 if( !variantName.IsEmpty() )
173 {
174 if( propName == _HKI( "Do not Populate" ) )
175 return wxVariant( footprint->GetDNPForVariant( variantName ) );
176 else if( propName == _HKI( "Exclude From Bill of Materials" ) )
177 return wxVariant( footprint->GetExcludedFromBOMForVariant( variantName ) );
178 else if( propName == _HKI( "Exclude From Simulation" ) )
179 return wxVariant( footprint->GetExcludedFromSimForVariant( variantName ) );
180 else if( propName == _HKI( "Exclude From Position Files" ) )
181 return wxVariant( footprint->GetExcludedFromPosFilesForVariant( variantName ) );
182 }
183 }
184
185 wxAny anyValue = aItem->Get( aProperty );
186
187 if( anyValue.IsNull() )
188 return wxVariant();
189
190 return anyToVariant( anyValue );
191}
192
193
194namespace
195{
196wxVariant getFootprintFieldValue( FOOTPRINT* aFootprint, const wxString& aFieldName )
197{
198 PCB_FIELD* field = aFootprint ? aFootprint->GetField( aFieldName ) : nullptr;
199
200 if( !field )
201 return wxVariant();
202
203 wxString variantName;
204
205 if( aFootprint->GetBoard() )
206 variantName = aFootprint->GetBoard()->GetCurrentVariant();
207
208 if( !variantName.IsEmpty() )
209 {
210 if( const FOOTPRINT_VARIANT* variant = aFootprint->GetVariant( variantName );
211 variant && variant->HasFieldValue( aFieldName ) )
212 {
213 return wxVariant( aFootprint->GetFieldValueForVariant( variantName, aFieldName ) );
214 }
215 }
216
217 return wxVariant( field->GetText() );
218}
219
220
221std::set<wxString> getCommonFootprintFieldNames( const PCB_SELECTION& aSelection )
222{
223 std::set<wxString> commonFieldNames;
224 bool firstFootprint = true;
225
226 for( EDA_ITEM* item : aSelection )
227 {
228 if( item->Type() != PCB_FOOTPRINT_T )
229 return {};
230
231 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
232 std::set<wxString> fieldNames;
233
234 for( PCB_FIELD* field : footprint->GetFields() )
235 {
236 if( field )
237 fieldNames.insert( field->GetUntranslatedName() );
238 }
239
240 if( firstFootprint )
241 {
242 commonFieldNames = std::move( fieldNames );
243 firstFootprint = false;
244 }
245 else
246 {
247 std::erase_if( commonFieldNames,
248 [&]( const wxString& aName )
249 {
250 return !fieldNames.count( aName );
251 } );
252 }
253 }
254
255 return commonFieldNames;
256}
257
258
259wxString formatFindByPropertiesDisplayValue( PCB_EDIT_FRAME* aFrame, PROPERTY_BASE* aProperty, const wxVariant& aValue )
260{
261 if( aValue.IsNull() )
262 return wxEmptyString;
263
264 if( !aProperty )
265 return aValue.GetString();
266
267 wxVariant valueCopy = aValue;
268 wxPGProperty* pgProperty = nullptr;
269
270 if( aProperty->TypeHash() == TYPE_HASH( PCB_LAYER_ID ) )
271 {
272 wxASSERT( aProperty->HasChoices() );
273
274 const wxPGChoices& canonicalLayers = aProperty->Choices();
275 wxArrayString boardLayerNames;
276 wxArrayInt boardLayerIDs;
277
278 for( int ii = 0; ii < (int) canonicalLayers.GetCount(); ++ii )
279 {
280 int layer = canonicalLayers.GetValue( ii );
281
282 boardLayerNames.push_back( aFrame->GetBoard()->GetLayerName( ToLAYER_ID( layer ) ) );
283 boardLayerIDs.push_back( layer );
284 }
285
286 auto layerProp = new PGPROPERTY_COLORENUM( new wxPGChoices( boardLayerNames, boardLayerIDs ) );
287 layerProp->SetLabel( wxGetTranslation( aProperty->Name() ) );
288 layerProp->SetName( aProperty->Name() );
289 layerProp->SetHelpString( wxGetTranslation( aProperty->Name() ) );
290 layerProp->SetClientData( const_cast<PROPERTY_BASE*>( aProperty ) );
291 pgProperty = layerProp;
292 }
293 else
294 {
295 pgProperty = PGPropertyFactory( aProperty, aFrame );
296 }
297
298 if( pgProperty )
299 {
300 wxString formatted = pgProperty->ValueToString( valueCopy, 0 );
301 delete pgProperty;
302
303 if( !formatted.IsEmpty() )
304 return formatted;
305 }
306
307 return aValue.GetString();
308}
309
310
311wxString normalizeFormattedValueForExpression( PROPERTY_BASE* aProperty, const wxString& aValue )
312{
313 wxString normalized = aValue;
314
315 switch( aProperty->Display() )
316 {
320 normalized.Replace( wxT( " " ), wxEmptyString );
321 normalized.Replace( wxT( "mils" ), wxT( "mil" ) );
322 break;
323
326 normalized.Replace( wxT( "°" ), wxT( "deg" ) );
327 normalized.Replace( wxT( " " ), wxEmptyString );
328 break;
329
330 default: break;
331 }
332
333 return normalized;
334}
335
336
337bool isExprIdentChar( wxChar aCh )
338{
339 return wxIsalnum( aCh ) || aCh == wxT( '_' );
340}
341
342
343std::set<wxString> getQueryableFootprintFieldNames( const std::vector<PROPERTY_ROW_DATA>& aRows )
344{
345 // Reference must stay a property lookup so A.Reference still matches R1's pads.
346 // getField() only answers on the footprint itself.
347 std::set<wxString> fieldNames = { _HKI( "Value" ), _HKI( "Datasheet" ), _HKI( "Description" ) };
348
349 for( const PROPERTY_ROW_DATA& row : aRows )
350 {
351 if( row.property == nullptr )
352 fieldNames.insert( row.propertyName );
353 }
354
355 return fieldNames;
356}
357
358
359bool matchAliasAt( const wxString& aExpression, size_t aPos, const wxString& aAlias )
360{
361 if( aPos + aAlias.length() > aExpression.length() )
362 return false;
363
364 if( aExpression.Mid( aPos, aAlias.length() ) != aAlias )
365 return false;
366
367 if( aPos > 0 && isExprIdentChar( aExpression[aPos - 1] ) )
368 return false;
369
370 size_t end = aPos + aAlias.length();
371
372 if( end < aExpression.length() && isExprIdentChar( aExpression[end] ) )
373 return false;
374
375 return true;
376}
377
378
379} // namespace
380
381
382wxString normalizeQueryFieldAliases( const wxString& aExpression, const std::vector<PROPERTY_ROW_DATA>& aRows )
383{
384 struct FIELD_ALIAS
385 {
386 wxString from;
387 wxString to;
388 };
389
390 std::vector<FIELD_ALIAS> aliases;
391
392 for( const wxString& fieldName : getQueryableFootprintFieldNames( aRows ) )
393 {
394 wxString exprName = fieldName;
395 exprName.Replace( wxT( " " ), wxT( "_" ) );
396
397 wxString escapedFieldName = fieldName;
398 escapedFieldName.Replace( wxT( "'" ), wxT( "\\'" ) );
399
400 aliases.push_back(
401 { wxT( "A." ) + exprName, wxString::Format( wxT( "A.getField('%s')" ), escapedFieldName ) } );
402 }
403
404 std::sort( aliases.begin(), aliases.end(),
405 []( const FIELD_ALIAS& aLhs, const FIELD_ALIAS& aRhs )
406 {
407 return aLhs.from.length() > aRhs.from.length();
408 } );
409
410 wxString normalized;
411 bool inString = false;
412
413 for( size_t i = 0; i < aExpression.length(); )
414 {
415 wxChar ch = aExpression[i];
416
417 if( ch == wxT( '\'' ) && ( i == 0 || aExpression[i - 1] != wxT( '\\' ) ) )
418 {
419 inString = !inString;
420 normalized += ch;
421 ++i;
422 continue;
423 }
424
425 if( !inString )
426 {
427 bool replaced = false;
428
429 for( const FIELD_ALIAS& alias : aliases )
430 {
431 if( matchAliasAt( aExpression, i, alias.from ) )
432 {
433 normalized += alias.to;
434 i += alias.from.length();
435 replaced = true;
436 break;
437 }
438 }
439
440 if( replaced )
441 continue;
442 }
443
444 normalized += ch;
445 ++i;
446 }
447
448 return normalized;
449}
450
451
452namespace
453{
454
455bool queryUsesUnsupportedPairwiseSyntax( const wxString& aExpression )
456{
457 bool inString = false;
458
459 for( size_t i = 0; i < aExpression.length(); ++i )
460 {
461 wxChar ch = aExpression[i];
462
463 if( ch == wxT( '\'' ) && ( i == 0 || aExpression[i - 1] != wxT( '\\' ) ) )
464 {
465 inString = !inString;
466 continue;
467 }
468
469 if( !inString && i + 2 <= aExpression.length() && aExpression.Mid( i, 2 ) == wxT( "B." )
470 && ( i == 0 || !isExprIdentChar( aExpression[i - 1] ) ) )
471 {
472 return true;
473 }
474 }
475
476 return false;
477}
478
479
480bool isVisibleInFindByProperties( const wxString& aName, const PROPERTY_BASE* aProperty )
481{
482 if( aProperty->IsHiddenFromPropertiesManager() && aName != wxS( "Type" ) )
483 return false;
484
485 if( aProperty->IsHiddenFromDesignEditors() )
486 return false;
487
488 return true;
489}
490} // namespace
491
492
494{
495 PCB_SELECTION_TOOL* selTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
496 const PCB_SELECTION& selection = selTool->GetSelection();
497
498 if( m_propertyGrid->GetNumberRows() > 0 )
499 m_propertyGrid->DeleteRows( 0, m_propertyGrid->GetNumberRows() );
500
501 if( selection.Empty() )
502 {
503 m_propertyRows.clear();
504 m_selectedTypes.clear();
505 m_statusLabel->SetLabel( _( "No items selected" ) );
506 m_createQueryBtn->Enable( false );
507 return;
508 }
509
510 std::map<wxString, PROPERTY_MATCH_MODE> savedModes;
511
512 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
513 {
514 if( row.matchMode != PROPERTY_MATCH_MODE::IGNORED )
515 savedModes[row.propertyName] = row.matchMode;
516 }
517
518 m_propertyRows.clear();
519 m_selectedTypes.clear();
520
521 if( selection.Size() == 1 )
522 m_statusLabel->SetLabel( selection.Front()->GetFriendlyName() );
523 else
524 m_statusLabel->SetLabel( wxString::Format( _( "%d objects selected" ), selection.Size() ) );
525
526 for( EDA_ITEM* item : selection )
527 m_selectedTypes.insert( TYPE_HASH( *item ) );
528
530
531 std::map<wxString, PROPERTY_BASE*> commonProps;
532 const std::vector<PROPERTY_BASE*>& firstProps = propMgr.GetProperties( *m_selectedTypes.begin() );
533
534 for( PROPERTY_BASE* prop : firstProps )
535 commonProps.emplace( prop->Name(), prop );
536
537 for( auto it = std::next( m_selectedTypes.begin() ); it != m_selectedTypes.end(); ++it )
538 {
539 for( auto propIt = commonProps.begin(); propIt != commonProps.end(); )
540 {
541 if( !propMgr.GetProperty( *it, propIt->first ) )
542 propIt = commonProps.erase( propIt );
543 else
544 ++propIt;
545 }
546 }
547
548 for( auto& [name, property] : commonProps )
549 {
550 if( !isVisibleInFindByProperties( name, property ) )
551 continue;
552
553 bool available = true;
554
555 for( EDA_ITEM* item : selection )
556 {
557 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), property, item ) )
558 {
559 available = false;
560 break;
561 }
562 }
563
564 if( !available )
565 continue;
566
568 row.propertyName = name;
569 row.property = property;
571 row.isMixed = false;
572
573 bool first = true;
574 bool different = false;
575
576 for( EDA_ITEM* item : selection )
577 {
578 PROPERTY_BASE* itemProp = propMgr.GetProperty( item, name );
579
580 if( !itemProp )
581 {
582 available = false;
583 break;
584 }
585
586 wxVariant value = getVariantAwareValue( item, itemProp );
587
588 if( value.IsNull() )
589 {
590 available = false;
591 break;
592 }
593
594 if( first )
595 {
596 row.rawValue = value;
597 first = false;
598 }
599 else if( !different && !row.rawValue.IsNull() && value != row.rawValue )
600 {
601 different = true;
602 row.rawValue.MakeNull();
603 }
604 }
605
606 if( !available )
607 continue;
608
609 row.isMixed = different;
610 row.displayValue = different ? wxString( wxT( "<...>" ) )
611 : formatFindByPropertiesDisplayValue( m_frame, row.property, row.rawValue );
612
613 m_propertyRows.push_back( row );
614 }
615
616 for( const wxString& fieldName : getCommonFootprintFieldNames( selection ) )
617 {
618 if( commonProps.count( fieldName ) )
619 continue;
620
622 row.propertyName = fieldName;
623 row.property = nullptr;
625 row.isMixed = false;
626
627 bool first = true;
628 bool different = false;
629 bool available = true;
630
631 for( EDA_ITEM* item : selection )
632 {
633 wxVariant value = getFootprintFieldValue( static_cast<FOOTPRINT*>( item ), fieldName );
634
635 if( value.IsNull() )
636 {
637 available = false;
638 break;
639 }
640
641 if( first )
642 {
643 row.rawValue = value;
644 first = false;
645 }
646 else if( !different && !row.rawValue.IsNull() && value != row.rawValue )
647 {
648 different = true;
649 row.rawValue.MakeNull();
650 }
651 }
652
653 if( !available )
654 continue;
655
656 row.isMixed = different;
657 row.displayValue = different ? wxString( wxT( "<...>" ) )
658 : formatFindByPropertiesDisplayValue( m_frame, row.property, row.rawValue );
659
660 m_propertyRows.push_back( row );
661 }
662
663 m_propertyGrid->AppendRows( m_propertyRows.size() );
664
665 for( int i = 0; i < (int) m_propertyRows.size(); i++ )
666 {
667 m_propertyGrid->SetCellValue( i, 0, wxGetTranslation( m_propertyRows[i].propertyName ) );
668 m_propertyGrid->SetCellValue( i, 1, m_propertyRows[i].displayValue );
669
670 m_propertyGrid->SetReadOnly( i, 0 );
671 m_propertyGrid->SetReadOnly( i, 1 );
672
673 if( m_propertyRows[i].isMixed )
674 {
675 m_propertyGrid->SetReadOnly( i, 2 );
676 m_propertyGrid->SetCellValue( i, 2, wxEmptyString );
677 m_propertyGrid->SetCellTextColour( i, 2, wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
678 }
679 else
680 {
681 wxString choices[] = { _( "Ignored" ), _( "Matching" ), _( "Different" ) };
682 m_propertyGrid->SetCellEditor( i, 2, new wxGridCellChoiceEditor( 3, choices ) );
683 m_propertyGrid->SetCellRenderer( i, 2, new wxGridCellStringRenderer() );
684
686 }
687 }
688
689 m_propertyGrid->AutoSizeColumns();
690 m_propertyGrid->SetColSize( 2, m_propertyGrid->GetTextExtent( _( "Different" ) ).GetWidth() + 40 );
691
692 int totalWidth = m_propertyGrid->GetClientSize().GetWidth();
693 int col0Width = m_propertyGrid->GetColSize( 0 );
694 int col2Width = m_propertyGrid->GetColSize( 2 );
695 int col1Width = totalWidth - col0Width - col2Width;
696
697 if( col1Width > m_propertyGrid->GetColSize( 1 ) )
698 m_propertyGrid->SetColSize( 1, col1Width );
699
700 bool anyActive = false;
701
702 for( int i = 0; i < (int) m_propertyRows.size(); i++ )
703 {
704 auto it = savedModes.find( m_propertyRows[i].propertyName );
705
706 if( it != savedModes.end() && !m_propertyRows[i].isMixed )
707 {
708 m_propertyRows[i].matchMode = it->second;
710 anyActive = true;
711 }
712 }
713
714 m_createQueryBtn->Enable( anyActive );
715}
716
717
719{
720 if( aEvent.GetCol() == 2 && aEvent.GetRow() >= 0 && aEvent.GetRow() < (int) m_propertyRows.size()
721 && !m_propertyRows[aEvent.GetRow()].isMixed )
722 {
723 m_propertyGrid->SetGridCursor( aEvent.GetRow(), aEvent.GetCol() );
724 m_propertyGrid->EnableCellEditControl();
725 }
726 else
727 {
728 aEvent.Skip();
729 }
730}
731
732
734{
735 int row = aEvent.GetRow();
736 int col = aEvent.GetCol();
737
738 if( col != 2 || row < 0 || row >= (int) m_propertyRows.size() )
739 {
740 aEvent.Skip();
741 return;
742 }
743
744 wxString val = m_propertyGrid->GetCellValue( row, 2 );
746
747 if( val == _( "Matching" ) )
749 else if( val == _( "Different" ) )
751 else
753
754 updateMatchModeCell( row );
755
756 bool anyActive = false;
757
758 for( const PROPERTY_ROW_DATA& r : m_propertyRows )
759 {
760 if( r.matchMode != PROPERTY_MATCH_MODE::IGNORED )
761 {
762 anyActive = true;
763 break;
764 }
765 }
766
767 m_createQueryBtn->Enable( anyActive );
768}
769
770
772{
773 int totalWidth = m_propertyGrid->GetClientSize().GetWidth();
774 int col0Width = m_propertyGrid->GetColSize( 0 );
775 int col2Width = m_propertyGrid->GetColSize( 2 );
776 int col1Width = totalWidth - col0Width - col2Width;
777
778 if( col1Width > 0 )
779 m_propertyGrid->SetColSize( 1, col1Width );
780
781 aEvent.Skip();
782}
783
784
786{
787 const PROPERTY_ROW_DATA& data = m_propertyRows[aRow];
788
789 switch( data.matchMode )
790 {
791 case PROPERTY_MATCH_MODE::IGNORED: m_propertyGrid->SetCellValue( aRow, 2, _( "Ignored" ) ); break;
792
793 case PROPERTY_MATCH_MODE::MATCHING: m_propertyGrid->SetCellValue( aRow, 2, _( "Matching" ) ); break;
794
795 case PROPERTY_MATCH_MODE::DIFFERENT: m_propertyGrid->SetCellValue( aRow, 2, _( "Different" ) ); break;
796 }
797
798 m_propertyGrid->ForceRefresh();
799}
800
801
803{
804 if( m_notebook->GetSelection() == 0 )
806 else
808}
809
810
812{
813 std::vector<BOARD_ITEM*> items;
814
815 if( !m_board )
816 return items;
817
818 for( PCB_TRACK* track : m_board->Tracks() )
819 items.push_back( track );
820
821 for( FOOTPRINT* fp : m_board->Footprints() )
822 {
823 items.push_back( fp );
824
825 for( PAD* pad : fp->Pads() )
826 items.push_back( pad );
827
828 for( PCB_FIELD* field : fp->GetFields() )
829 items.push_back( field );
830
831 for( BOARD_ITEM* gi : fp->GraphicalItems() )
832 items.push_back( gi );
833
834 for( ZONE* zone : fp->Zones() )
835 items.push_back( zone );
836 }
837
838 for( BOARD_ITEM* item : m_board->Drawings() )
839 items.push_back( item );
840
841 for( ZONE* zone : m_board->Zones() )
842 items.push_back( zone );
843
844 for( PCB_GROUP* group : m_board->Groups() )
845 items.push_back( group );
846
847 return items;
848}
849
850
852{
853 if( m_selectedTypes.find( TYPE_HASH( *aItem ) ) == m_selectedTypes.end() )
854 return false;
855
857
858 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
859 {
860 if( row.matchMode == PROPERTY_MATCH_MODE::IGNORED )
861 continue;
862
863 if( row.isMixed )
864 continue;
865
866 wxVariant itemValue;
867
868 if( row.property == nullptr )
869 {
870 if( aItem->Type() != PCB_FOOTPRINT_T )
871 return false;
872
873 itemValue = getFootprintFieldValue( static_cast<FOOTPRINT*>( aItem ), row.propertyName );
874 }
875 else
876 {
877 PROPERTY_BASE* prop = propMgr.GetProperty( aItem, row.propertyName );
878
879 if( !prop )
880 return false;
881
882 if( !propMgr.IsAvailableFor( TYPE_HASH( *aItem ), prop, aItem ) )
883 return false;
884
885 itemValue = getVariantAwareValue( aItem, prop );
886 }
887
888 if( itemValue.IsNull() )
889 return false;
890
891 bool valuesEqual = ( itemValue == row.rawValue );
892
893 if( row.matchMode == PROPERTY_MATCH_MODE::MATCHING && !valuesEqual )
894 return false;
895
896 if( row.matchMode == PROPERTY_MATCH_MODE::DIFFERENT && valuesEqual )
897 return false;
898 }
899
900 return true;
901}
902
903
904void DIALOG_FIND_BY_PROPERTIES::applyMatchResults( EDA_ITEMS& aMatchList, wxStaticText* aStatusLabel )
905{
906 TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
907
908 if( m_deselectNonMatching->IsChecked() )
910
911 if( !aMatchList.empty() )
912 toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &aMatchList );
913
914 if( m_zoomToFit->IsChecked() && !aMatchList.empty() )
916
917 m_frame->GetCanvas()->ForceRefresh();
918
919 aStatusLabel->SetLabel( wxString::Format( _( "%zu items matched" ), aMatchList.size() ) );
920}
921
922
924{
925 std::vector<BOARD_ITEM*> allItems = collectAllBoardItems();
926 EDA_ITEMS matchList;
927
928 for( BOARD_ITEM* item : allItems )
929 {
930 if( itemMatchesPropertyCriteria( item ) )
931 matchList.push_back( item );
932 }
933
934 applyMatchResults( matchList, m_statusLabel );
935}
936
937
939{
940 wxString expression = m_queryEditor->GetText().Trim().Trim( false );
941
942 if( expression.IsEmpty() )
943 return;
944
945 wxString normalizedExpression = normalizeQueryFieldAliases( expression, m_propertyRows );
946
947 if( queryUsesUnsupportedPairwiseSyntax( normalizedExpression ) )
948 {
949 wxString error = _( "B. expressions are not supported." );
950
951 m_queryStatusLabel->SetLabel( error );
952 wxMessageBox( error, _( "Expression Error" ), wxOK | wxICON_ERROR, this );
953 return;
954 }
955
956 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
957 PCBEXPR_UCODE ucode;
958 PCBEXPR_CONTEXT preflightContext( 0, F_Cu );
959
960 wxString errors;
961
962 compiler.SetErrorCallback(
963 [&]( const wxString& aMessage, int aOffset )
964 {
965 errors += aMessage + wxT( "\n" );
966 } );
967
968 bool ok = compiler.Compile( normalizedExpression.ToUTF8().data(), &ucode, &preflightContext );
969
970 if( !ok )
971 {
972 m_queryStatusLabel->SetLabel( _( "Syntax error in expression" ) );
973 wxMessageBox( errors, _( "Expression Error" ), wxOK | wxICON_ERROR, this );
974 return;
975 }
976
977 saveRecentQuery( expression );
978
979 std::vector<BOARD_ITEM*> allItems = collectAllBoardItems();
980 EDA_ITEMS matchList;
981
982 for( BOARD_ITEM* item : allItems )
983 {
984 bool matched = false;
985 LSET itemLayers = item->GetLayerSet();
986
987 for( PCB_LAYER_ID layer : itemLayers.Seq() )
988 {
989 PCBEXPR_CONTEXT ctx( 0, layer );
990 ctx.SetItems( item, nullptr );
991
992 LIBEVAL::VALUE* result = ucode.Run( &ctx );
993
994 if( result && result->AsDouble() != 0.0 )
995 {
996 matched = true;
997 break;
998 }
999 }
1000
1001 if( matched )
1002 matchList.push_back( item );
1003 }
1004
1006}
1007
1008
1009wxString DIALOG_FIND_BY_PROPERTIES::propNameToExprField( const wxString& aPropName )
1010{
1011 wxString result = aPropName;
1012 result.Replace( wxT( " " ), wxT( "_" ) );
1013 return result;
1014}
1015
1016
1018{
1019 if( aValue.GetType() == wxT( "bool" ) )
1020 return aValue.GetBool() ? wxT( "true" ) : wxT( "false" );
1021
1022 if( aProp && aProp->HasChoices() )
1023 {
1024 wxString val = formatFindByPropertiesDisplayValue( m_frame, aProp, aValue );
1025
1026 val.Replace( wxT( "'" ), wxT( "\\'" ) );
1027
1028 return wxString::Format( wxT( "'%s'" ), val );
1029 }
1030
1031 if( aProp )
1032 {
1033 switch( aProp->Display() )
1034 {
1040 return normalizeFormattedValueForExpression( aProp,
1041 formatFindByPropertiesDisplayValue( m_frame, aProp, aValue ) );
1042
1043 default: break;
1044 }
1045 }
1046
1047 if( aValue.GetType() == wxT( "double" ) || aValue.GetType() == wxT( "long" ) )
1048 return aValue.GetString();
1049
1050 wxString val = aProp ? formatFindByPropertiesDisplayValue( m_frame, aProp, aValue ) : aValue.GetString();
1051
1052 val.Replace( wxT( "'" ), wxT( "\\'" ) );
1053
1054 return wxString::Format( wxT( "'%s'" ), val );
1055}
1056
1057
1059{
1060 wxArrayString conditions;
1061
1062 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
1063 {
1064 if( row.matchMode == PROPERTY_MATCH_MODE::IGNORED )
1065 continue;
1066
1067 if( row.isMixed )
1068 continue;
1069
1070 wxString lhs;
1071
1072 if( row.property == nullptr )
1073 {
1074 lhs = wxString::Format( wxT( "A.getField(%s)" ),
1075 formatValueForExpression( nullptr, wxVariant( row.propertyName ) ) );
1076 }
1077 else
1078 {
1079 lhs = wxString::Format( wxT( "A.%s" ), propNameToExprField( row.propertyName ) );
1080 }
1081
1082 wxString value = formatValueForExpression( row.property, row.rawValue );
1083
1084 if( value.IsEmpty() )
1085 {
1086 if( aSkippedRows )
1087 aSkippedRows->Add( wxGetTranslation( row.propertyName ) );
1088
1089 continue;
1090 }
1091
1092 wxString op = ( row.matchMode == PROPERTY_MATCH_MODE::MATCHING ) ? wxT( "==" ) : wxT( "!=" );
1093
1094 conditions.Add( wxString::Format( wxT( "%s %s %s" ), lhs, op, value ) );
1095 }
1096
1097 wxString result;
1098
1099 for( size_t i = 0; i < conditions.size(); i++ )
1100 {
1101 if( i > 0 )
1102 result += wxT( " && " );
1103
1104 result += conditions[i];
1105 }
1106
1107 return result;
1108}
1109
1110
1112{
1113 wxArrayString skipped;
1114 wxString expr = generateExpressionFromProperties( &skipped );
1115
1116 if( !expr.IsEmpty() || !skipped.empty() )
1117 {
1118 m_queryEditor->SetText( expr );
1119 m_notebook->SetSelection( 1 );
1120
1121 if( skipped.empty() )
1122 m_queryStatusLabel->SetLabel( wxEmptyString );
1123 else if( skipped.size() == 1 )
1124 m_queryStatusLabel->SetLabel( wxString::Format( _( "No value to match: %s" ), skipped[0] ) );
1125 else
1126 m_queryStatusLabel->SetLabel( wxString::Format( _( "%zu rows have no value to match" ), skipped.size() ) );
1127 }
1128}
1129
1130
1132{
1133 wxString expression = m_queryEditor->GetText().Trim().Trim( false );
1134
1135 if( expression.IsEmpty() )
1136 {
1137 m_queryStatusLabel->SetLabel( _( "Expression is empty" ) );
1138 return;
1139 }
1140
1141 wxString normalizedExpression = normalizeQueryFieldAliases( expression, m_propertyRows );
1142
1143 if( queryUsesUnsupportedPairwiseSyntax( normalizedExpression ) )
1144 {
1145 m_queryStatusLabel->SetLabel( _( "B. expressions are not supported." ) );
1146 return;
1147 }
1148
1149 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
1150 PCBEXPR_UCODE ucode;
1151 PCBEXPR_CONTEXT preflightContext( 0, F_Cu );
1152 wxString errors;
1153
1154 compiler.SetErrorCallback(
1155 [&]( const wxString& aMessage, int aOffset )
1156 {
1157 errors += aMessage + wxT( "\n" );
1158 } );
1159
1160 bool ok = compiler.Compile( normalizedExpression.ToUTF8().data(), &ucode, &preflightContext );
1161
1162 if( ok )
1163 m_queryStatusLabel->SetLabel( _( "Syntax OK" ) );
1164 else
1165 m_queryStatusLabel->SetLabel( _( "Syntax error: " ) + errors );
1166}
1167
1168
1170{
1171 int sel = m_recentQueries->GetSelection();
1172
1173 if( sel != wxNOT_FOUND )
1174 m_queryEditor->SetText( m_recentQueries->GetString( sel ) );
1175}
1176
1177
1179{
1180 m_recentQueries->Clear();
1181
1182 PROJECT_FILE& prj = m_frame->Prj().GetProjectFile();
1183
1184 for( const wxString& query : prj.m_FindByPropertiesQueries )
1185 m_recentQueries->Append( query );
1186}
1187
1188
1190{
1191 if( aQuery.IsEmpty() )
1192 return;
1193
1194 PROJECT_FILE& prj = m_frame->Prj().GetProjectFile();
1195 auto& queries = prj.m_FindByPropertiesQueries;
1196
1197 // Remove duplicate if exists
1198 queries.erase( std::remove( queries.begin(), queries.end(), aQuery ), queries.end() );
1199
1200 // Insert at front
1201 queries.insert( queries.begin(), aQuery );
1202
1203 // Cap at 10
1204 if( queries.size() > 10 )
1205 queries.resize( 10 );
1206
1208 m_recentQueries->SetSelection( 0 );
1209}
1210
1211
1213{
1214 Show( false );
1215}
1216
1217
1219{
1220 if( event.GetSelection() == 0 )
1222
1223 event.Skip();
1224}
1225
1226
1228{
1229 int pos = m_queryEditor->GetCurrentPos();
1230
1231 if( pos < 2 )
1232 return;
1233
1234 wxString prev2 = m_queryEditor->GetTextRange( pos - 2, pos );
1235
1236 if( prev2 == wxT( "A." ) )
1237 {
1239 wxArrayString tokens;
1240 std::set<wxString> seen;
1241
1242 // Gather property names from known board item types
1243 std::vector<TYPE_ID> types = { TYPE_HASH( PCB_TRACK ), TYPE_HASH( PCB_VIA ), TYPE_HASH( PCB_ARC ),
1246
1247 for( TYPE_ID type : types )
1248 {
1249 for( PROPERTY_BASE* prop : propMgr.GetProperties( type ) )
1250 {
1251 wxString name = prop->Name();
1252
1253 if( !isVisibleInFindByProperties( name, prop ) )
1254 continue;
1255
1256 name.Replace( wxT( " " ), wxT( "_" ) );
1257
1258 if( seen.insert( name ).second )
1259 tokens.Add( name );
1260 }
1261 }
1262
1263 for( const wxString& fieldName : getQueryableFootprintFieldNames( m_propertyRows ) )
1264 {
1265 wxString exprName = fieldName;
1266 exprName.Replace( wxT( " " ), wxT( "_" ) );
1267
1268 if( seen.insert( exprName ).second )
1269 tokens.Add( exprName );
1270 }
1271
1272 tokens.Sort();
1273 m_scintillaTricks->DoAutocomplete( wxEmptyString, tokens );
1274 }
1275}
const char * name
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
static TOOL_ACTION zoomFitSelection
Definition actions.h:140
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:228
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:943
wxString GetCurrentVariant() const
Definition board.h:529
DIALOG_FIND_BY_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Find by Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
bool Show(bool show=true) override
DIALOG_FIND_BY_PROPERTIES(PCB_EDIT_FRAME *aParent)
void onSelectMatchingClick(wxCommandEvent &event) override
void applyMatchResults(EDA_ITEMS &aMatchList, wxStaticText *aStatusLabel)
wxString generateExpressionFromProperties(wxArrayString *aSkippedRows=nullptr)
std::vector< PROPERTY_ROW_DATA > m_propertyRows
void onGridCellChanged(wxGridEvent &aEvent)
void saveRecentQuery(const wxString &aQuery)
wxString formatValueForExpression(PROPERTY_BASE *aProp, const wxVariant &aValue)
wxVariant getVariantAwareValue(EDA_ITEM *aItem, PROPERTY_BASE *aProperty)
void onNotebookPageChanged(wxNotebookEvent &event) override
void OnBoardChanged(wxCommandEvent &event)
void onCheckSyntaxClick(wxCommandEvent &event) override
static wxVariant anyToVariant(const wxAny &aValue)
bool itemMatchesPropertyCriteria(BOARD_ITEM *aItem)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
static wxString propNameToExprField(const wxString &aPropName)
void onRecentQuerySelected(wxCommandEvent &event) override
void OnCloseButtonClick(wxCommandEvent &event) override
void onGridCellClick(wxGridEvent &aEvent)
std::vector< BOARD_ITEM * > collectAllBoardItems()
void onGridSizeChanged(wxSizeEvent &aEvent)
void onCreateQueryClick(wxCommandEvent &event) override
bool Show(bool show) override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
Variant information for a footprint.
Definition footprint.h:228
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:287
bool GetExcludedFromSimForVariant(const wxString &aVariantName) const
Get the exclude-from-simulation status for a specific variant.
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
wxString GetFieldValueForVariant(const wxString &aVariantName, const wxString &aFieldName) const
Get a field value for a specific variant.
bool GetDNPForVariant(const wxString &aVariantName) const
Get the DNP status for a specific variant.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
bool GetExcludedFromPosFilesForVariant(const wxString &aVariantName) const
Get the exclude-from-position-files status for a specific variant.
bool GetExcludedFromBOMForVariant(const wxString &aVariantName) const
Get the exclude-from-BOM status for a specific variant.
wxAny Get(PROPERTY_BASE *aProperty) const
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
VALUE * Run(CONTEXT *ctx)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
Definition pad.h:61
void SetItems(BOARD_ITEM *a, BOARD_ITEM *b=nullptr)
BOARD * GetBoard() const
The main frame for Pcbnew.
wxString GetUntranslatedName() const
Get the untranslated field name for storage, variable look-up, etc.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
A wxEnumProperty that displays a color next to the enum value.
The backing store for a PROJECT, in JSON format.
std::vector< wxString > m_FindByPropertiesQueries
Recent queries for Find by Properties dialog.
bool IsHiddenFromPropertiesManager() const
Definition property.h:324
virtual size_t TypeHash() const =0
Return type-id of the property type.
bool IsHiddenFromDesignEditors() const
Definition property.h:345
PROPERTY_DISPLAY Display() const
Definition property.h:314
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition property.h:247
const wxString & Name() const
Definition property.h:221
virtual const wxPGChoices & Choices() const
Return a limited set of possible values (e.g.
Definition property.h:227
Provide class metadata.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Return a property for a specific type.
bool IsAvailableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
Add cut/copy/paste, dark theme, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
Master controller class:
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
wxString normalizeQueryFieldAliases(const wxString &aExpression, const std::vector< PROPERTY_ROW_DATA > &aRows)
wxString normalizeQueryFieldAliases(const wxString &aExpression, const std::vector< PROPERTY_ROW_DATA > &aRows)
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Cu
Definition layer_ids.h:60
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
#define _HKI(x)
Definition page_info.cpp:40
Class to handle a set of BOARD_ITEMs.
wxPGProperty * PGPropertyFactory(const PROPERTY_BASE *aProperty, EDA_DRAW_FRAME *aFrame)
#define TYPE_HASH(x)
Macro to generate unique identifier for a type.
Definition property.h:74
@ PT_DEGREE
Angle expressed in degrees.
Definition property.h:66
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition property.h:65
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition property.h:67
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:63
@ PT_TIME
Time expressed in ps.
Definition property.h:69
size_t TYPE_ID
Unique type identifier.
std::vector< EDA_ITEM * > EDA_ITEMS
std::vector< FAB_LAYER_COLOR > dummy
PROPERTY_MATCH_MODE matchMode
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78