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