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 std::set<wxString> fieldNames = { _HKI( "Reference" ), _HKI( "Value" ), _HKI( "Datasheet" ),
346 _HKI( "Description" ) };
347
348 for( const PROPERTY_ROW_DATA& row : aRows )
349 {
350 if( row.property == nullptr )
351 fieldNames.insert( row.propertyName );
352 }
353
354 return fieldNames;
355}
356
357
358bool matchAliasAt( const wxString& aExpression, size_t aPos, const wxString& aAlias )
359{
360 if( aPos + aAlias.length() > aExpression.length() )
361 return false;
362
363 if( aExpression.Mid( aPos, aAlias.length() ) != aAlias )
364 return false;
365
366 if( aPos > 0 && isExprIdentChar( aExpression[aPos - 1] ) )
367 return false;
368
369 size_t end = aPos + aAlias.length();
370
371 if( end < aExpression.length() && isExprIdentChar( aExpression[end] ) )
372 return false;
373
374 return true;
375}
376
377
378wxString normalizeQueryFieldAliases( const wxString& aExpression, const std::vector<PROPERTY_ROW_DATA>& aRows )
379{
380 struct FIELD_ALIAS
381 {
382 wxString from;
383 wxString to;
384 };
385
386 std::vector<FIELD_ALIAS> aliases;
387
388 for( const wxString& fieldName : getQueryableFootprintFieldNames( aRows ) )
389 {
390 wxString exprName = fieldName;
391 exprName.Replace( wxT( " " ), wxT( "_" ) );
392
393 wxString escapedFieldName = fieldName;
394 escapedFieldName.Replace( wxT( "'" ), wxT( "\\'" ) );
395
396 aliases.push_back(
397 { wxT( "A." ) + exprName, wxString::Format( wxT( "A.getField('%s')" ), escapedFieldName ) } );
398 }
399
400 std::sort( aliases.begin(), aliases.end(),
401 []( const FIELD_ALIAS& aLhs, const FIELD_ALIAS& aRhs )
402 {
403 return aLhs.from.length() > aRhs.from.length();
404 } );
405
406 wxString normalized;
407 bool inString = false;
408
409 for( size_t i = 0; i < aExpression.length(); )
410 {
411 wxChar ch = aExpression[i];
412
413 if( ch == wxT( '\'' ) && ( i == 0 || aExpression[i - 1] != wxT( '\\' ) ) )
414 {
415 inString = !inString;
416 normalized += ch;
417 ++i;
418 continue;
419 }
420
421 if( !inString )
422 {
423 bool replaced = false;
424
425 for( const FIELD_ALIAS& alias : aliases )
426 {
427 if( matchAliasAt( aExpression, i, alias.from ) )
428 {
429 normalized += alias.to;
430 i += alias.from.length();
431 replaced = true;
432 break;
433 }
434 }
435
436 if( replaced )
437 continue;
438 }
439
440 normalized += ch;
441 ++i;
442 }
443
444 return normalized;
445}
446
447
448bool queryUsesUnsupportedPairwiseSyntax( const wxString& aExpression )
449{
450 bool inString = false;
451
452 for( size_t i = 0; i < aExpression.length(); ++i )
453 {
454 wxChar ch = aExpression[i];
455
456 if( ch == wxT( '\'' ) && ( i == 0 || aExpression[i - 1] != wxT( '\\' ) ) )
457 {
458 inString = !inString;
459 continue;
460 }
461
462 if( !inString && i + 2 <= aExpression.length() && aExpression.Mid( i, 2 ) == wxT( "B." )
463 && ( i == 0 || !isExprIdentChar( aExpression[i - 1] ) ) )
464 {
465 return true;
466 }
467 }
468
469 return false;
470}
471
472
473bool isVisibleInFindByProperties( const wxString& aName, const PROPERTY_BASE* aProperty )
474{
475 if( aProperty->IsHiddenFromPropertiesManager() && aName != wxS( "Type" ) )
476 return false;
477
478 if( aProperty->IsHiddenFromDesignEditors() )
479 return false;
480
481 return true;
482}
483} // namespace
484
485
487{
488 PCB_SELECTION_TOOL* selTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
489 const PCB_SELECTION& selection = selTool->GetSelection();
490
491 if( m_propertyGrid->GetNumberRows() > 0 )
492 m_propertyGrid->DeleteRows( 0, m_propertyGrid->GetNumberRows() );
493
494 if( selection.Empty() )
495 {
496 m_propertyRows.clear();
497 m_selectedTypes.clear();
498 m_statusLabel->SetLabel( _( "No items selected" ) );
499 m_createQueryBtn->Enable( false );
500 return;
501 }
502
503 std::map<wxString, PROPERTY_MATCH_MODE> savedModes;
504
505 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
506 {
507 if( row.matchMode != PROPERTY_MATCH_MODE::IGNORED )
508 savedModes[row.propertyName] = row.matchMode;
509 }
510
511 m_propertyRows.clear();
512 m_selectedTypes.clear();
513
514 if( selection.Size() == 1 )
515 m_statusLabel->SetLabel( selection.Front()->GetFriendlyName() );
516 else
517 m_statusLabel->SetLabel( wxString::Format( _( "%d objects selected" ), selection.Size() ) );
518
519 for( EDA_ITEM* item : selection )
520 m_selectedTypes.insert( TYPE_HASH( *item ) );
521
523
524 std::map<wxString, PROPERTY_BASE*> commonProps;
525 const std::vector<PROPERTY_BASE*>& firstProps = propMgr.GetProperties( *m_selectedTypes.begin() );
526
527 for( PROPERTY_BASE* prop : firstProps )
528 commonProps.emplace( prop->Name(), prop );
529
530 for( auto it = std::next( m_selectedTypes.begin() ); it != m_selectedTypes.end(); ++it )
531 {
532 for( auto propIt = commonProps.begin(); propIt != commonProps.end(); )
533 {
534 if( !propMgr.GetProperty( *it, propIt->first ) )
535 propIt = commonProps.erase( propIt );
536 else
537 ++propIt;
538 }
539 }
540
541 for( auto& [name, property] : commonProps )
542 {
543 if( !isVisibleInFindByProperties( name, property ) )
544 continue;
545
546 bool available = true;
547
548 for( EDA_ITEM* item : selection )
549 {
550 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), property, item ) )
551 {
552 available = false;
553 break;
554 }
555 }
556
557 if( !available )
558 continue;
559
561 row.propertyName = name;
562 row.property = property;
564 row.isMixed = false;
565
566 bool first = true;
567 bool different = false;
568
569 for( EDA_ITEM* item : selection )
570 {
571 PROPERTY_BASE* itemProp = propMgr.GetProperty( item, name );
572
573 if( !itemProp )
574 {
575 available = false;
576 break;
577 }
578
579 wxVariant value = getVariantAwareValue( item, itemProp );
580
581 if( value.IsNull() )
582 {
583 available = false;
584 break;
585 }
586
587 if( first )
588 {
589 row.rawValue = value;
590 first = false;
591 }
592 else if( !different && !row.rawValue.IsNull() && value != row.rawValue )
593 {
594 different = true;
595 row.rawValue.MakeNull();
596 }
597 }
598
599 if( !available )
600 continue;
601
602 row.isMixed = different;
603 row.displayValue = different ? wxString( wxT( "<...>" ) )
604 : formatFindByPropertiesDisplayValue( m_frame, row.property, row.rawValue );
605
606 m_propertyRows.push_back( row );
607 }
608
609 for( const wxString& fieldName : getCommonFootprintFieldNames( selection ) )
610 {
611 if( commonProps.count( fieldName ) )
612 continue;
613
615 row.propertyName = fieldName;
616 row.property = nullptr;
618 row.isMixed = false;
619
620 bool first = true;
621 bool different = false;
622 bool available = true;
623
624 for( EDA_ITEM* item : selection )
625 {
626 wxVariant value = getFootprintFieldValue( static_cast<FOOTPRINT*>( item ), fieldName );
627
628 if( value.IsNull() )
629 {
630 available = false;
631 break;
632 }
633
634 if( first )
635 {
636 row.rawValue = value;
637 first = false;
638 }
639 else if( !different && !row.rawValue.IsNull() && value != row.rawValue )
640 {
641 different = true;
642 row.rawValue.MakeNull();
643 }
644 }
645
646 if( !available )
647 continue;
648
649 row.isMixed = different;
650 row.displayValue = different ? wxString( wxT( "<...>" ) )
651 : formatFindByPropertiesDisplayValue( m_frame, row.property, row.rawValue );
652
653 m_propertyRows.push_back( row );
654 }
655
656 m_propertyGrid->AppendRows( m_propertyRows.size() );
657
658 for( int i = 0; i < (int) m_propertyRows.size(); i++ )
659 {
660 m_propertyGrid->SetCellValue( i, 0, wxGetTranslation( m_propertyRows[i].propertyName ) );
661 m_propertyGrid->SetCellValue( i, 1, m_propertyRows[i].displayValue );
662
663 m_propertyGrid->SetReadOnly( i, 0 );
664 m_propertyGrid->SetReadOnly( i, 1 );
665
666 if( m_propertyRows[i].isMixed )
667 {
668 m_propertyGrid->SetReadOnly( i, 2 );
669 m_propertyGrid->SetCellValue( i, 2, wxEmptyString );
670 m_propertyGrid->SetCellTextColour( i, 2, wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
671 }
672 else
673 {
674 wxString choices[] = { _( "Ignored" ), _( "Matching" ), _( "Different" ) };
675 m_propertyGrid->SetCellEditor( i, 2, new wxGridCellChoiceEditor( 3, choices ) );
676 m_propertyGrid->SetCellRenderer( i, 2, new wxGridCellStringRenderer() );
677
679 }
680 }
681
682 m_propertyGrid->AutoSizeColumns();
683 m_propertyGrid->SetColSize( 2, m_propertyGrid->GetTextExtent( _( "Different" ) ).GetWidth() + 40 );
684
685 int totalWidth = m_propertyGrid->GetClientSize().GetWidth();
686 int col0Width = m_propertyGrid->GetColSize( 0 );
687 int col2Width = m_propertyGrid->GetColSize( 2 );
688 int col1Width = totalWidth - col0Width - col2Width;
689
690 if( col1Width > m_propertyGrid->GetColSize( 1 ) )
691 m_propertyGrid->SetColSize( 1, col1Width );
692
693 bool anyActive = false;
694
695 for( int i = 0; i < (int) m_propertyRows.size(); i++ )
696 {
697 auto it = savedModes.find( m_propertyRows[i].propertyName );
698
699 if( it != savedModes.end() && !m_propertyRows[i].isMixed )
700 {
701 m_propertyRows[i].matchMode = it->second;
703 anyActive = true;
704 }
705 }
706
707 m_createQueryBtn->Enable( anyActive );
708}
709
710
712{
713 if( aEvent.GetCol() == 2 && aEvent.GetRow() >= 0 && aEvent.GetRow() < (int) m_propertyRows.size()
714 && !m_propertyRows[aEvent.GetRow()].isMixed )
715 {
716 m_propertyGrid->SetGridCursor( aEvent.GetRow(), aEvent.GetCol() );
717 m_propertyGrid->EnableCellEditControl();
718 }
719 else
720 {
721 aEvent.Skip();
722 }
723}
724
725
727{
728 int row = aEvent.GetRow();
729 int col = aEvent.GetCol();
730
731 if( col != 2 || row < 0 || row >= (int) m_propertyRows.size() )
732 {
733 aEvent.Skip();
734 return;
735 }
736
737 wxString val = m_propertyGrid->GetCellValue( row, 2 );
739
740 if( val == _( "Matching" ) )
742 else if( val == _( "Different" ) )
744 else
746
747 updateMatchModeCell( row );
748
749 bool anyActive = false;
750
751 for( const PROPERTY_ROW_DATA& r : m_propertyRows )
752 {
753 if( r.matchMode != PROPERTY_MATCH_MODE::IGNORED )
754 {
755 anyActive = true;
756 break;
757 }
758 }
759
760 m_createQueryBtn->Enable( anyActive );
761}
762
763
765{
766 int totalWidth = m_propertyGrid->GetClientSize().GetWidth();
767 int col0Width = m_propertyGrid->GetColSize( 0 );
768 int col2Width = m_propertyGrid->GetColSize( 2 );
769 int col1Width = totalWidth - col0Width - col2Width;
770
771 if( col1Width > 0 )
772 m_propertyGrid->SetColSize( 1, col1Width );
773
774 aEvent.Skip();
775}
776
777
779{
780 const PROPERTY_ROW_DATA& data = m_propertyRows[aRow];
781
782 switch( data.matchMode )
783 {
784 case PROPERTY_MATCH_MODE::IGNORED: m_propertyGrid->SetCellValue( aRow, 2, _( "Ignored" ) ); break;
785
786 case PROPERTY_MATCH_MODE::MATCHING: m_propertyGrid->SetCellValue( aRow, 2, _( "Matching" ) ); break;
787
788 case PROPERTY_MATCH_MODE::DIFFERENT: m_propertyGrid->SetCellValue( aRow, 2, _( "Different" ) ); break;
789 }
790
791 m_propertyGrid->ForceRefresh();
792}
793
794
796{
797 if( m_notebook->GetSelection() == 0 )
799 else
801}
802
803
805{
806 std::vector<BOARD_ITEM*> items;
807
808 if( !m_board )
809 return items;
810
811 for( PCB_TRACK* track : m_board->Tracks() )
812 items.push_back( track );
813
814 for( FOOTPRINT* fp : m_board->Footprints() )
815 {
816 items.push_back( fp );
817
818 for( PAD* pad : fp->Pads() )
819 items.push_back( pad );
820
821 for( PCB_FIELD* field : fp->GetFields() )
822 items.push_back( field );
823
824 for( BOARD_ITEM* gi : fp->GraphicalItems() )
825 items.push_back( gi );
826
827 for( ZONE* zone : fp->Zones() )
828 items.push_back( zone );
829 }
830
831 for( BOARD_ITEM* item : m_board->Drawings() )
832 items.push_back( item );
833
834 for( ZONE* zone : m_board->Zones() )
835 items.push_back( zone );
836
837 for( PCB_GROUP* group : m_board->Groups() )
838 items.push_back( group );
839
840 return items;
841}
842
843
845{
846 if( m_selectedTypes.find( TYPE_HASH( *aItem ) ) == m_selectedTypes.end() )
847 return false;
848
850
851 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
852 {
853 if( row.matchMode == PROPERTY_MATCH_MODE::IGNORED )
854 continue;
855
856 if( row.isMixed )
857 continue;
858
859 wxVariant itemValue;
860
861 if( row.property == nullptr )
862 {
863 if( aItem->Type() != PCB_FOOTPRINT_T )
864 return false;
865
866 itemValue = getFootprintFieldValue( static_cast<FOOTPRINT*>( aItem ), row.propertyName );
867 }
868 else
869 {
870 PROPERTY_BASE* prop = propMgr.GetProperty( aItem, row.propertyName );
871
872 if( !prop )
873 return false;
874
875 if( !propMgr.IsAvailableFor( TYPE_HASH( *aItem ), prop, aItem ) )
876 return false;
877
878 itemValue = getVariantAwareValue( aItem, prop );
879 }
880
881 if( itemValue.IsNull() )
882 return false;
883
884 bool valuesEqual = ( itemValue == row.rawValue );
885
886 if( row.matchMode == PROPERTY_MATCH_MODE::MATCHING && !valuesEqual )
887 return false;
888
889 if( row.matchMode == PROPERTY_MATCH_MODE::DIFFERENT && valuesEqual )
890 return false;
891 }
892
893 return true;
894}
895
896
897void DIALOG_FIND_BY_PROPERTIES::applyMatchResults( EDA_ITEMS& aMatchList, wxStaticText* aStatusLabel )
898{
899 TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
900
901 if( m_deselectNonMatching->IsChecked() )
903
904 if( !aMatchList.empty() )
905 toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &aMatchList );
906
907 if( m_zoomToFit->IsChecked() && !aMatchList.empty() )
909
910 m_frame->GetCanvas()->ForceRefresh();
911
912 aStatusLabel->SetLabel( wxString::Format( _( "%zu items matched" ), aMatchList.size() ) );
913}
914
915
917{
918 std::vector<BOARD_ITEM*> allItems = collectAllBoardItems();
919 EDA_ITEMS matchList;
920
921 for( BOARD_ITEM* item : allItems )
922 {
923 if( itemMatchesPropertyCriteria( item ) )
924 matchList.push_back( item );
925 }
926
927 applyMatchResults( matchList, m_statusLabel );
928}
929
930
932{
933 wxString expression = m_queryEditor->GetText().Trim().Trim( false );
934
935 if( expression.IsEmpty() )
936 return;
937
938 wxString normalizedExpression = normalizeQueryFieldAliases( expression, m_propertyRows );
939
940 if( queryUsesUnsupportedPairwiseSyntax( normalizedExpression ) )
941 {
942 wxString error = _( "B. expressions are not supported." );
943
944 m_queryStatusLabel->SetLabel( error );
945 wxMessageBox( error, _( "Expression Error" ), wxOK | wxICON_ERROR, this );
946 return;
947 }
948
949 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
950 PCBEXPR_UCODE ucode;
951 PCBEXPR_CONTEXT preflightContext( 0, F_Cu );
952
953 wxString errors;
954
955 compiler.SetErrorCallback(
956 [&]( const wxString& aMessage, int aOffset )
957 {
958 errors += aMessage + wxT( "\n" );
959 } );
960
961 bool ok = compiler.Compile( normalizedExpression.ToUTF8().data(), &ucode, &preflightContext );
962
963 if( !ok )
964 {
965 m_queryStatusLabel->SetLabel( _( "Syntax error in expression" ) );
966 wxMessageBox( errors, _( "Expression Error" ), wxOK | wxICON_ERROR, this );
967 return;
968 }
969
970 saveRecentQuery( expression );
971
972 std::vector<BOARD_ITEM*> allItems = collectAllBoardItems();
973 EDA_ITEMS matchList;
974
975 for( BOARD_ITEM* item : allItems )
976 {
977 bool matched = false;
978 LSET itemLayers = item->GetLayerSet();
979
980 for( PCB_LAYER_ID layer : itemLayers.Seq() )
981 {
982 PCBEXPR_CONTEXT ctx( 0, layer );
983 ctx.SetItems( item, nullptr );
984
985 LIBEVAL::VALUE* result = ucode.Run( &ctx );
986
987 if( result && result->AsDouble() != 0.0 )
988 {
989 matched = true;
990 break;
991 }
992 }
993
994 if( matched )
995 matchList.push_back( item );
996 }
997
999}
1000
1001
1002wxString DIALOG_FIND_BY_PROPERTIES::propNameToExprField( const wxString& aPropName )
1003{
1004 wxString result = aPropName;
1005 result.Replace( wxT( " " ), wxT( "_" ) );
1006 return result;
1007}
1008
1009
1011{
1012 if( aValue.GetType() == wxT( "bool" ) )
1013 return aValue.GetBool() ? wxT( "true" ) : wxT( "false" );
1014
1015 if( aProp && aProp->HasChoices() )
1016 {
1017 wxString val = formatFindByPropertiesDisplayValue( m_frame, aProp, aValue );
1018
1019 val.Replace( wxT( "'" ), wxT( "\\'" ) );
1020
1021 return wxString::Format( wxT( "'%s'" ), val );
1022 }
1023
1024 if( aProp )
1025 {
1026 switch( aProp->Display() )
1027 {
1033 return normalizeFormattedValueForExpression( aProp,
1034 formatFindByPropertiesDisplayValue( m_frame, aProp, aValue ) );
1035
1036 default: break;
1037 }
1038 }
1039
1040 if( aValue.GetType() == wxT( "double" ) || aValue.GetType() == wxT( "long" ) )
1041 return aValue.GetString();
1042
1043 wxString val = aProp ? formatFindByPropertiesDisplayValue( m_frame, aProp, aValue ) : aValue.GetString();
1044
1045 val.Replace( wxT( "'" ), wxT( "\\'" ) );
1046
1047 return wxString::Format( wxT( "'%s'" ), val );
1048}
1049
1050
1052{
1053 wxArrayString conditions;
1054
1055 for( const PROPERTY_ROW_DATA& row : m_propertyRows )
1056 {
1057 if( row.matchMode == PROPERTY_MATCH_MODE::IGNORED )
1058 continue;
1059
1060 if( row.isMixed )
1061 continue;
1062
1063 wxString lhs;
1064
1065 if( row.property == nullptr )
1066 {
1067 lhs = wxString::Format( wxT( "A.getField(%s)" ),
1068 formatValueForExpression( nullptr, wxVariant( row.propertyName ) ) );
1069 }
1070 else
1071 {
1072 lhs = wxString::Format( wxT( "A.%s" ), propNameToExprField( row.propertyName ) );
1073 }
1074
1075 wxString value = formatValueForExpression( row.property, row.rawValue );
1076
1077 if( value.IsEmpty() )
1078 {
1079 if( aSkippedRows )
1080 aSkippedRows->Add( wxGetTranslation( row.propertyName ) );
1081
1082 continue;
1083 }
1084
1085 wxString op = ( row.matchMode == PROPERTY_MATCH_MODE::MATCHING ) ? wxT( "==" ) : wxT( "!=" );
1086
1087 conditions.Add( wxString::Format( wxT( "%s %s %s" ), lhs, op, value ) );
1088 }
1089
1090 wxString result;
1091
1092 for( size_t i = 0; i < conditions.size(); i++ )
1093 {
1094 if( i > 0 )
1095 result += wxT( " && " );
1096
1097 result += conditions[i];
1098 }
1099
1100 return result;
1101}
1102
1103
1105{
1106 wxArrayString skipped;
1107 wxString expr = generateExpressionFromProperties( &skipped );
1108
1109 if( !expr.IsEmpty() || !skipped.empty() )
1110 {
1111 m_queryEditor->SetText( expr );
1112 m_notebook->SetSelection( 1 );
1113
1114 if( skipped.empty() )
1115 m_queryStatusLabel->SetLabel( wxEmptyString );
1116 else if( skipped.size() == 1 )
1117 m_queryStatusLabel->SetLabel( wxString::Format( _( "No value to match: %s" ), skipped[0] ) );
1118 else
1119 m_queryStatusLabel->SetLabel( wxString::Format( _( "%zu rows have no value to match" ), skipped.size() ) );
1120 }
1121}
1122
1123
1125{
1126 wxString expression = m_queryEditor->GetText().Trim().Trim( false );
1127
1128 if( expression.IsEmpty() )
1129 {
1130 m_queryStatusLabel->SetLabel( _( "Expression is empty" ) );
1131 return;
1132 }
1133
1134 wxString normalizedExpression = normalizeQueryFieldAliases( expression, m_propertyRows );
1135
1136 if( queryUsesUnsupportedPairwiseSyntax( normalizedExpression ) )
1137 {
1138 m_queryStatusLabel->SetLabel( _( "B. expressions are not supported." ) );
1139 return;
1140 }
1141
1142 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
1143 PCBEXPR_UCODE ucode;
1144 PCBEXPR_CONTEXT preflightContext( 0, F_Cu );
1145 wxString errors;
1146
1147 compiler.SetErrorCallback(
1148 [&]( const wxString& aMessage, int aOffset )
1149 {
1150 errors += aMessage + wxT( "\n" );
1151 } );
1152
1153 bool ok = compiler.Compile( normalizedExpression.ToUTF8().data(), &ucode, &preflightContext );
1154
1155 if( ok )
1156 m_queryStatusLabel->SetLabel( _( "Syntax OK" ) );
1157 else
1158 m_queryStatusLabel->SetLabel( _( "Syntax error: " ) + errors );
1159}
1160
1161
1163{
1164 int sel = m_recentQueries->GetSelection();
1165
1166 if( sel != wxNOT_FOUND )
1167 m_queryEditor->SetText( m_recentQueries->GetString( sel ) );
1168}
1169
1170
1172{
1173 m_recentQueries->Clear();
1174
1175 PROJECT_FILE& prj = m_frame->Prj().GetProjectFile();
1176
1177 for( const wxString& query : prj.m_FindByPropertiesQueries )
1178 m_recentQueries->Append( query );
1179}
1180
1181
1183{
1184 if( aQuery.IsEmpty() )
1185 return;
1186
1187 PROJECT_FILE& prj = m_frame->Prj().GetProjectFile();
1188 auto& queries = prj.m_FindByPropertiesQueries;
1189
1190 // Remove duplicate if exists
1191 queries.erase( std::remove( queries.begin(), queries.end(), aQuery ), queries.end() );
1192
1193 // Insert at front
1194 queries.insert( queries.begin(), aQuery );
1195
1196 // Cap at 10
1197 if( queries.size() > 10 )
1198 queries.resize( 10 );
1199
1201 m_recentQueries->SetSelection( 0 );
1202}
1203
1204
1206{
1207 Show( false );
1208}
1209
1210
1212{
1213 if( event.GetSelection() == 0 )
1215
1216 event.Skip();
1217}
1218
1219
1221{
1222 int pos = m_queryEditor->GetCurrentPos();
1223
1224 if( pos < 2 )
1225 return;
1226
1227 wxString prev2 = m_queryEditor->GetTextRange( pos - 2, pos );
1228
1229 if( prev2 == wxT( "A." ) )
1230 {
1232 wxArrayString tokens;
1233 std::set<wxString> seen;
1234
1235 // Gather property names from known board item types
1236 std::vector<TYPE_ID> types = { TYPE_HASH( PCB_TRACK ), TYPE_HASH( PCB_VIA ), TYPE_HASH( PCB_ARC ),
1239
1240 for( TYPE_ID type : types )
1241 {
1242 for( PROPERTY_BASE* prop : propMgr.GetProperties( type ) )
1243 {
1244 wxString name = prop->Name();
1245
1246 if( !isVisibleInFindByProperties( name, prop ) )
1247 continue;
1248
1249 name.Replace( wxT( " " ), wxT( "_" ) );
1250
1251 if( seen.insert( name ).second )
1252 tokens.Add( name );
1253 }
1254 }
1255
1256 for( const wxString& fieldName : getQueryableFootprintFieldNames( m_propertyRows ) )
1257 {
1258 wxString exprName = fieldName;
1259 exprName.Replace( wxT( " " ), wxT( "_" ) );
1260
1261 if( seen.insert( exprName ).second )
1262 tokens.Add( exprName );
1263 }
1264
1265 tokens.Sort();
1266 m_scintillaTricks->DoAutocomplete( wxEmptyString, tokens );
1267 }
1268}
const char * name
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:936
wxString GetCurrentVariant() const
Definition board.h:521
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:227
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:286
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()
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.Helper macro to map type hashes to names.
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
#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)
Customized abstract wxPGProperty class to handle coordinate/size units.
#define TYPE_HASH(x)
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