/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include int SCH_FIND_REPLACE_TOOL::FindAndReplace( const TOOL_EVENT& aEvent ) { m_frame->ShowFindReplaceDialog( aEvent.IsAction( &ACTIONS::findAndReplace ) ); return UpdateFind( aEvent ); } int SCH_FIND_REPLACE_TOOL::UpdateFind( const TOOL_EVENT& aEvent ) { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); SCH_SEARCH_DATA* schSearchData = dynamic_cast( &data ); bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false; auto visit = [&]( EDA_ITEM* aItem, SCH_SHEET_PATH* aSheet ) { // We may get triggered when the dialog is not opened due to binding // SelectedItemsModified we also get triggered when the find dialog is // closed....so we need to double check the dialog is open. if( m_frame->m_findReplaceDialog != nullptr && !data.findString.IsEmpty() && aItem->Matches( data, aSheet ) && ( !selectedOnly || aItem->IsSelected() ) ) { aItem->SetForceVisible( true ); m_selectionTool->BrightenItem( aItem ); m_foundItemHighlighted = true; } else if( aItem->IsBrightened() ) { aItem->SetForceVisible( false ); m_selectionTool->UnbrightenItem( aItem ); } }; auto visitAll = [&]() { for( SCH_ITEM* item : m_frame->GetScreen()->Items() ) { visit( item, &m_frame->GetCurrentSheet() ); item->RunOnChildren( [&]( SCH_ITEM* aChild ) { visit( aChild, &m_frame->GetCurrentSheet() ); } ); } }; if( aEvent.IsAction( &ACTIONS::find ) || aEvent.IsAction( &ACTIONS::findAndReplace ) || aEvent.IsAction( &ACTIONS::updateFind ) ) { m_foundItemHighlighted = false; visitAll(); } else if( aEvent.Matches( EVENTS::SelectedItemsModified ) ) { for( EDA_ITEM* item : m_selectionTool->GetSelection() ) visit( item, &m_frame->GetCurrentSheet() ); } else if( aEvent.Matches( EVENTS::PointSelectedEvent ) || aEvent.Matches( EVENTS::SelectedEvent ) || aEvent.Matches( EVENTS::UnselectedEvent ) || aEvent.Matches( EVENTS::ClearedEvent ) ) { if( !m_frame->m_findReplaceDialog ) { if( m_foundItemHighlighted ) { m_foundItemHighlighted = false; visitAll(); } } else if( selectedOnly ) { // Normal find modifies the selection, but selection-based find does not, so we want // to start over in the items we are searching through when the selection changes m_afterItem = nullptr; visitAll(); } } else if( m_foundItemHighlighted ) { m_foundItemHighlighted = false; visitAll(); } getView()->UpdateItems(); m_frame->GetCanvas()->Refresh(); m_frame->updateTitle(); return 0; } SCH_ITEM* SCH_FIND_REPLACE_TOOL::nextMatch( SCH_SCREEN* aScreen, SCH_SHEET_PATH* aSheet, SCH_ITEM* aAfter, EDA_SEARCH_DATA& aData, bool reversed ) { SCH_SEARCH_DATA* schSearchData = dynamic_cast( &aData ); bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false; bool past_item = !aAfter; std::vector sorted_items; auto addItem = [&](SCH_ITEM* item) { sorted_items.push_back( item ); if( item->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* cmp = static_cast( item ); for( SCH_FIELD& field : cmp->GetFields() ) sorted_items.push_back( &field ); for( SCH_PIN* pin : cmp->GetPins() ) sorted_items.push_back( pin ); } else if( item->Type() == SCH_SHEET_T ) { SCH_SHEET* sheet = static_cast( item ); for( SCH_FIELD& field : sheet->GetFields() ) sorted_items.push_back( &field ); for( SCH_SHEET_PIN* pin : sheet->GetPins() ) sorted_items.push_back( pin ); } else if( item->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) { SCH_LABEL_BASE* label = static_cast( item ); for( SCH_FIELD& field : label->GetFields() ) sorted_items.push_back( &field ); } }; if( selectedOnly ) for( EDA_ITEM* item : m_selectionTool->GetSelection() ) addItem( static_cast( item ) ); else for( SCH_ITEM* item : aScreen->Items() ) addItem( item ); std::sort( sorted_items.begin(), sorted_items.end(), [&]( SCH_ITEM* a, SCH_ITEM* b ) { if( a->GetPosition().x == b->GetPosition().x ) { // Ensure deterministic sort if( a->GetPosition().y == b->GetPosition().y ) return a->m_Uuid < b->m_Uuid; return a->GetPosition().y < b->GetPosition().y; } else return a->GetPosition().x < b->GetPosition().x; } ); if( reversed ) std::reverse( sorted_items.begin(), sorted_items.end() ); for( SCH_ITEM* item : sorted_items ) { if( item == aAfter ) { past_item = true; } else if( past_item ) { if( aData.markersOnly && item->Type() == SCH_MARKER_T ) return item; if( item->Matches( aData, aSheet ) ) return item; } } return nullptr; } int SCH_FIND_REPLACE_TOOL::FindNext( const TOOL_EVENT& aEvent ) { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); bool searchAllSheets = false; bool selectedOnly = false; bool isReversed = aEvent.IsAction( &ACTIONS::findPrevious ); SCH_ITEM* item = nullptr; SCH_SHEET_PATH* afterSheet = &m_frame->GetCurrentSheet(); try { const SCH_SEARCH_DATA& schSearchData = dynamic_cast( data ); searchAllSheets = !( schSearchData.searchCurrentSheetOnly ); selectedOnly = schSearchData.searchSelectedOnly; } catch( const std::bad_cast& ) { } if( aEvent.IsAction( &ACTIONS::findNextMarker ) ) data.markersOnly = true; else if( data.findString.IsEmpty() ) return FindAndReplace( ACTIONS::find.MakeEvent() ); if( m_wrapAroundTimer.IsRunning() ) { afterSheet = nullptr; m_afterItem = nullptr; m_wrapAroundTimer.Stop(); m_frame->ClearFindReplaceStatus(); } if( afterSheet || !searchAllSheets ) { item = nextMatch( m_frame->GetScreen(), &m_frame->GetCurrentSheet(), m_afterItem, data, isReversed ); } if( !item && searchAllSheets ) { SCH_SCREENS screens( m_frame->Schematic().Root() ); std::vector paths; screens.BuildClientSheetPathList(); for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() ) { for( SCH_SHEET_PATH& sheet : screen->GetClientSheetPaths() ) paths.push_back( &sheet ); } std::sort( paths.begin(), paths.end(), [] ( const SCH_SHEET_PATH* lhs, const SCH_SHEET_PATH* rhs ) -> bool { int retval = lhs->ComparePageNum( *rhs ); if( retval < 0 ) return true; else if( retval > 0 ) return false; else /// Enforce strict ordering. If the page numbers are the same, use UUIDs return lhs->GetCurrentHash() < rhs->GetCurrentHash(); } ); if( isReversed ) std::reverse( paths.begin(), paths.end() ); for( SCH_SHEET_PATH* sheet : paths ) { if( afterSheet ) { if( afterSheet->GetCurrentHash() == sheet->GetCurrentHash() ) afterSheet = nullptr; continue; } item = nextMatch( sheet->LastScreen(), sheet, nullptr, data, isReversed ); if( item ) { if( m_frame->Schematic().CurrentSheet() != *sheet ) { m_frame->Schematic().SetCurrentSheet( *sheet ); m_frame->DisplayCurrentSheet(); } break; } } } if( item ) { m_afterItem = item; if( !item->IsBrightened() ) { // Clear any previous brightening UpdateFind( aEvent ); // Brighten (and show) found object item->SetForceVisible( true ); m_selectionTool->BrightenItem( item ); m_foundItemHighlighted = true; } if( !selectedOnly ) { m_selectionTool->ClearSelection(); m_selectionTool->AddItemToSel( item ); } m_frame->FocusOnLocation( item->GetBoundingBox().GetCenter() ); m_frame->GetCanvas()->Refresh(); } else { wxString msg = searchAllSheets ? _( "Reached end of schematic." ) : _( "Reached end of sheet." ); // Show the popup during the time period the user can wrap the search m_frame->ShowFindReplaceStatus( msg + wxS( " " ) + _( "Find again to wrap around to the start." ), 4000 ); m_wrapAroundTimer.StartOnce( 4000 ); } return 0; } EDA_ITEM* SCH_FIND_REPLACE_TOOL::getCurrentMatch() { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); SCH_SEARCH_DATA* schSearchData = dynamic_cast( &data ); bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false; return selectedOnly ? m_afterItem : m_selectionTool->GetSelection().Front(); } bool SCH_FIND_REPLACE_TOOL::HasMatch() { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); EDA_ITEM* match = getCurrentMatch(); return match && match->Matches( data, &m_frame->GetCurrentSheet() ); } int SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext( const TOOL_EVENT& aEvent ) { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); EDA_ITEM* item = getCurrentMatch(); SCH_SHEET_PATH* sheet = &m_frame->GetCurrentSheet(); if( data.findString.IsEmpty() ) return FindAndReplace( ACTIONS::find.MakeEvent() ); // TODO: move to SCH_COMMIT.... if( item && HasMatch() ) { SCH_ITEM* sch_item = static_cast( item ); m_frame->SaveCopyInUndoList( sheet->LastScreen(), sch_item, UNDO_REDO::CHANGED, false ); if( item->Replace( data, sheet ) ) { m_frame->UpdateItem( item, false, true ); m_frame->GetCurrentSheet().UpdateAllScreenReferences(); m_frame->OnModify(); } FindNext( ACTIONS::findNext.MakeEvent() ); } return 0; } int SCH_FIND_REPLACE_TOOL::ReplaceAll( const TOOL_EVENT& aEvent ) { EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData(); bool currentSheetOnly = false; bool selectedOnly = false; try { const SCH_SEARCH_DATA& schSearchData = dynamic_cast( data ); currentSheetOnly = schSearchData.searchCurrentSheetOnly; selectedOnly = schSearchData.searchSelectedOnly; } catch( const std::bad_cast& ) { } bool modified = false; // TODO: move to SCH_COMMIT.... if( data.findString.IsEmpty() ) return FindAndReplace( ACTIONS::find.MakeEvent() ); auto doReplace = [&]( SCH_ITEM* aItem, SCH_SHEET_PATH* aSheet, EDA_SEARCH_DATA& aData ) { m_frame->SaveCopyInUndoList( aSheet->LastScreen(), aItem, UNDO_REDO::CHANGED, modified ); if( aItem->Replace( aData, aSheet ) ) { m_frame->UpdateItem( aItem, false, true ); modified = true; } }; if( currentSheetOnly || selectedOnly ) { SCH_SHEET_PATH* currentSheet = &m_frame->GetCurrentSheet(); SCH_ITEM* item = nextMatch( m_frame->GetScreen(), currentSheet, nullptr, data, false ); while( item ) { if( !selectedOnly || item->IsSelected() ) doReplace( item, currentSheet, data ); item = nextMatch( m_frame->GetScreen(), currentSheet, item, data, false ); } } else { SCH_SHEET_LIST allSheets = m_frame->Schematic().GetSheets(); SCH_SCREENS screens( m_frame->Schematic().Root() ); for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() ) { SCH_SHEET_LIST sheets = allSheets.FindAllSheetsForScreen( screen ); for( unsigned ii = 0; ii < sheets.size(); ++ii ) { SCH_ITEM* item = nextMatch( screen, &sheets[ii], nullptr, data, false ); while( item ) { if( ii == 0 ) { doReplace( item, &sheets[0], data ); } else if( item->Type() == SCH_FIELD_T ) { SCH_FIELD* field = static_cast( item ); if( field->GetParent() && field->GetParent()->Type() == SCH_SYMBOL_T ) { switch( field->GetId() ) { case REFERENCE_FIELD: case VALUE_FIELD: case FOOTPRINT_FIELD: // must be handled for each distinct sheet doReplace( field, &sheets[ii], data ); break; default: // handled in first iteration break; } } } item = nextMatch( screen, &sheets[ii], item, data, false ); } } } } if( modified ) { m_frame->GetCurrentSheet().UpdateAllScreenReferences(); m_frame->OnModify(); } return 0; } void SCH_FIND_REPLACE_TOOL::setTransitions() { Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::find.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::findAndReplace.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNext.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findPrevious.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNextMarker.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext, ACTIONS::replaceAndFindNext.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::ReplaceAll, ACTIONS::replaceAll.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, ACTIONS::updateFind.MakeEvent() ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedItemsModified ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::PointSelectedEvent ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedEvent ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::UnselectedEvent ); Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::ClearedEvent ); }