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