KiCad PCB EDA Suite
Loading...
Searching...
No Matches
match_properties_tool.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 <board.h>
23#include <board_commit.h>
24#include <collectors.h>
25#include <dialog_shim.h>
26#include <pgm_base.h>
27#include <pcbnew_settings.h>
31#include <tool/tool_manager.h>
32#include <kiplatform/ui.h>
34#include <status_popup.h>
35#include <tools/hover_picker.h>
37#include <tools/pcb_actions.h>
40#include <view/view_controls.h>
41
42#include <algorithm>
43
44#include <wx/checklst.h>
45#include <wx/clntdata.h>
46#include <wx/dialog.h>
47#include <wx/menu.h>
48#include <wx/sizer.h>
49#include <wx/stattext.h>
50
51
55{
56public:
57 MATCH_PROPERTIES_DIALOG( wxWindow* aParent, std::set<wxString>& aEnabled, const wxString& aFamily ) :
58 DIALOG_SHIM( aParent, wxID_ANY, _( "Match Properties Settings" ), wxDefaultPosition, wxDefaultSize,
59 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
60 m_enabled( aEnabled )
61 {
62 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
63
64 sizer->Add( new wxStaticText( this, wxID_ANY,
65 wxString::Format( _( "Properties copied from %s:" ),
67 0, wxALL, 10 );
68
69 m_properties = new wxCheckListBox( this, wxID_ANY );
70 m_properties->SetMinSize( FromDIP( wxSize( 380, 320 ) ) );
71
72 std::vector<std::pair<wxString, wxString>> rows;
73
74 for( const wxString& key : MATCH_PROPERTIES_CATALOG::AllSafeKeys() )
75 {
76 if( MATCH_PROPERTIES_CATALOG::FamiliesFor( key ).contains( aFamily ) )
77 rows.emplace_back( MATCH_PROPERTIES_CATALOG::PropertyLabel( key ), key );
78 }
79
80 std::ranges::sort( rows );
81
82 for( const auto& [label, key] : rows )
83 {
84 const unsigned int index = m_properties->Append( label, new wxStringClientData( key ) );
85
86 m_properties->Check( index, m_enabled.contains( key ) );
87 }
88
89 sizer->Add( m_properties, 1, wxEXPAND | wxLEFT | wxRIGHT, 10 );
90 sizer->Add( CreateStdDialogButtonSizer( wxOK | wxCANCEL ), 0, wxEXPAND | wxALL, 10 );
91 SetSizer( sizer );
92
93 m_properties->Bind( wxEVT_CONTEXT_MENU, &MATCH_PROPERTIES_DIALOG::onContextMenu, this );
94
97 }
98
99 bool TransferDataFromWindow() override
100 {
101 // Only the rows on screen are answered for. Another kind's properties were never shown,
102 // so clearing the set wholesale would silently turn them all off.
103 for( unsigned int ii = 0; ii < m_properties->GetCount(); ++ii )
104 {
105 const wxString key = keyAt( ii );
106
107 if( m_properties->IsChecked( ii ) )
108 m_enabled.insert( key );
109 else
110 m_enabled.erase( key );
111 }
112
113 return true;
114 }
115
116private:
117 wxString keyAt( unsigned int aIndex ) const
118 {
119 auto* data = static_cast<wxStringClientData*>( m_properties->GetClientObject( aIndex ) );
120
121 return data ? data->GetData() : wxString();
122 }
123
124 void checkAll( bool aChecked )
125 {
126 for( unsigned int ii = 0; ii < m_properties->GetCount(); ++ii )
127 m_properties->Check( ii, aChecked );
128 }
129
130 void onContextMenu( wxContextMenuEvent& aEvent )
131 {
132 wxMenu menu;
133
134 enum
135 {
136 ID_CHECK_ALL = wxID_HIGHEST + 1,
137 ID_UNCHECK_ALL
138 };
139
140 menu.Append( ID_CHECK_ALL, _( "Check All" ) );
141 menu.Append( ID_UNCHECK_ALL, _( "Uncheck All" ) );
142
143 switch( GetPopupMenuSelectionFromUser( menu ) )
144 {
145 case ID_CHECK_ALL: checkAll( true ); break;
146 case ID_UNCHECK_ALL: checkAll( false ); break;
147 default: break;
148 }
149 }
150
151 wxCheckListBox* m_properties;
152 std::set<wxString>& m_enabled;
153};
154
155
157static bool hasMatchableSource( const SELECTION& aSelection )
158{
159 if( aSelection.Size() != 1 )
160 return false;
161
162 return !MATCH_PROPERTIES_CATALOG::Family( *aSelection.Front() ).IsEmpty();
163}
164
165
167 PCB_TOOL_BASE( "pcbnew.MatchProperties" )
168{
169}
170
171
173{
175
176 // Settings live in the Edit menu only, like every other tool.
177 CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
178
180
181 return true;
182}
183
184
185const std::set<wxString>& MATCH_PROPERTIES_TOOL::enabledKeys()
186{
187 PCB_VIEWERS_SETTINGS_BASE* settings = frame()->GetViewerSettingsBase();
188
189 return settings ? settings->m_MatchProperties : MATCH_PROPERTIES_CATALOG::DefaultKeys();
190}
191
192
193bool MATCH_PROPERTIES_TOOL::applyToTargets( const EDA_ITEM& aSource, const std::vector<EDA_ITEM*>& aTargets )
194{
195 if( aTargets.empty() )
196 return false;
197
198 const std::set<wxString>& enabled = enabledKeys();
199 BOARD_COMMIT commit( this );
200 PROPERTY_COMMIT_HANDLER handler( &commit );
201 wxString error;
202 int changed = 0;
203
204 // CompatibleTargets() already reduced this to board items of the source family.
205 for( EDA_ITEM* target : aTargets )
206 {
207 wxCHECK2( target->IsBOARD_ITEM(), continue );
208
209 commit.Modify( static_cast<BOARD_ITEM*>( target ) );
210
211 // Copy() stages on a clone and writes only if every value validates. A refusal leaves
212 // this target untouched. Revert() undoes the earlier ones.
214
215 if( !result )
216 {
217 error = result.m_Error;
218 break;
219 }
220
221 changed += result.m_Changed;
222 }
223
224 if( !error.IsEmpty() )
225 frame()->ShowInfoBarError( error );
226 else if( changed == 0 )
227 frame()->ShowInfoBarMsg( _( "Nothing to copy. The target already matches the source." ) );
228
229 if( !error.IsEmpty() || changed == 0 )
230 {
231 commit.Revert();
232 return false;
233 }
234
235 commit.Push( _( "Match Properties" ) );
236 return true;
237}
238
239
241{
242 PCB_SELECTION& selection = m_selectionTool->GetSelection();
243
244 // The tool copies from one item to many, so the one it copies from has to be settled before
245 // it starts. The context menu says the same, but the hotkey can arrive any time.
246 if( selection.Size() != 1 )
247 {
248 frame()->ShowInfoBarError( _( "Select one item to copy properties from." ) );
249 return 0;
250 }
251
252 EDA_ITEM* source = selection.Front();
253
254 if( MATCH_PROPERTIES_CATALOG::Family( *source ).IsEmpty() )
255 {
256 frame()->ShowInfoBarError( _( "The selected source item has no properties to match." ) );
257 return 0;
258 }
259
260 // Not a reason to refuse. The settings only open from inside the tool, so there would
261 // be no way left to turn anything on.
263 {
264 frame()->ShowInfoBarMsg( _( "No properties are enabled for this kind of item. Press <ctrl>+<,> to "
265 "choose some." ) );
266 }
267
268 return runInteractive( aEvent, source->m_Uuid );
269}
270
271
272int MATCH_PROPERTIES_TOOL::runInteractive( const TOOL_EVENT& aEvent, const KIID& aSourceId )
273{
274 BOARD* board = frame()->GetBoard();
275 PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
276 HOVER_PICKER hover( m_toolMgr );
277 STATUS_TEXT_POPUP statusPopup( frame() );
278 const KIID sourceId = aSourceId;
279 bool done = false;
280
281 // Settled before the run started, so it can only go missing under a commit or an undo.
282 auto currentSource = [&]() -> BOARD_ITEM*
283 {
284 return board->ResolveItem( sourceId, true );
285 };
286
287 // How much of the enabled set this kind of source can actually offer. Saying the number
288 // beats making the user open the dialog to find out nothing is turned on.
289 auto enabledCount = [&]( const EDA_ITEM& aItem )
290 {
291 const wxString family = MATCH_PROPERTIES_CATALOG::Family( aItem );
292 int count = 0;
293
294 for( const wxString& key : enabledKeys() )
295 {
296 if( MATCH_PROPERTIES_CATALOG::FamiliesFor( key ).contains( family ) )
297 count++;
298 }
299
300 return count;
301 };
302
303 auto prompt = [&]()
304 {
305 if( BOARD_ITEM* source = currentSource() )
306 {
307 statusPopup.SetText( wxString::Format( _( "Click or drag over the items to copy to.\n"
308 "%d properties enabled; <ctrl>+<,> for settings." ),
309 enabledCount( *source ) ) );
310 }
311 };
312
314 auto accepts = [&]( BOARD_ITEM* aSource )
315 {
316 return [aSource]( BOARD_ITEM& aItem )
317 {
318 return &aItem != aSource && MATCH_PROPERTIES_CATALOG::Compatible( *aSource, aItem );
319 };
320 };
321
322 // The source keeps a mark of its own for the whole run. Without it there is nothing on
323 // screen saying what the properties are being copied from.
324 auto updateHover = [&]( const VECTOR2I& aPointer )
325 {
326 BOARD_ITEM* source = currentSource();
327
328 hover.ClearBrightening();
329 statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
330
331 // An undo can take the source out from under the run. Nothing is a target without one
332 // to compare against.
333 if( !source )
334 return;
335
336 hover.Brighten( source );
337
338 if( BOARD_ITEM* target = hover.Pick( aPointer, accepts( source ) ) )
339 hover.Brighten( target );
340 };
341
342 Activate();
343
344 // The selection tool arms its disambiguation on button-down unless a tool owns the stack.
345 // Without this the applying click also selects, which cancels the picker.
346 frame()->PushTool( aEvent );
347
348 m_selectionTool->ClearSelection();
349 prompt();
350 statusPopup.Popup();
351 statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
352 canvas()->SetStatusPopup( statusPopup.GetPanel() );
353
354 picker->SetCursor( KICURSOR::BULLSEYE );
355
356 // Snapping on is what makes the picker honour the modifiers that turn it off again, so
357 // <shift> and <ctrl> give a finer aim among crowded items.
358 picker->SetSnapping( true );
359
360 // The pointer only names an item, so the lines the snap system draws are just noise.
361 picker->SetConstructionGeometry( false );
362 picker->ClearHandlers();
363 picker->SetMotionHandler(
364 [&]( const VECTOR2D& aPointer )
365 {
366 updateHover( aPointer );
367 } );
368 picker->SetClickHandler(
369 [&]( const VECTOR2D& aPointer ) -> bool
370 {
371 BOARD_ITEM* source = currentSource();
372
373 if( !source )
374 {
375 frame()->ShowInfoBarError( _( "The Match Properties source no longer exists." ) );
376 return false;
377 }
378
379 // The click takes what the hover lit, so what was shown is what is changed.
380 BOARD_ITEM* target = hover.Pick( aPointer, accepts( source ) );
381
382 if( !target )
383 return true;
384
385 // The commit may replace either item, so let go of every highlight first.
386 hover.ClearBrightening();
387 applyToTargets( *source, MATCH_PROPERTIES_CATALOG::CompatibleTargets( *source, { target } ) );
388 updateHover( aPointer );
389 return true;
390 } );
391
392 // Light the items the box has caught so far, so a drag shows its reach as it grows.
393 picker->SetAreaPreviewHandler(
395 {
396 BOARD_ITEM* source = currentSource();
397
398 if( !source )
399 return;
400
401 std::vector<BOARD_ITEM*> lit{ source };
402
403 for( BOARD_ITEM* item : m_selectionTool->CollectMultiple( aArea ) )
404 {
405 if( item != source && MATCH_PROPERTIES_CATALOG::Compatible( *source, *item ) )
406 lit.push_back( item );
407 }
408
409 // The box grows by a little on every motion event, so most of this set was
410 // already lit a moment ago.
411 hover.BrightenOnly( lit );
412 } );
413 picker->SetAreaHandler(
414 [&]() -> bool
415 {
416 BOARD_ITEM* source = currentSource();
417
418 if( !source )
419 return false;
420
421 const PCB_SELECTION& selected = m_selectionTool->GetSelection();
422 std::vector<EDA_ITEM*> candidates( selected.begin(), selected.end() );
423
424 hover.ClearBrightening();
425 applyToTargets( *source, MATCH_PROPERTIES_CATALOG::CompatibleTargets( *source, candidates ) );
426 m_selectionTool->ClearSelection();
427 return true;
428 } );
429 picker->SetCancelHandler(
430 [&]()
431 {
432 hover.ClearBrightening();
433 } );
434 picker->SetFinalizeHandler(
435 [&]( const int& )
436 {
437 hover.ClearBrightening();
438 done = true;
439 } );
440 m_toolMgr->RunAction( ACTIONS::pickerSubTool );
441
442 while( !done )
443 {
444 TOOL_EVENT* event = Wait();
445
446 if( !event )
447 break;
448
449 // Handled here rather than passed on. The tool is already inside this coroutine,
450 // and letting the action dispatch to it would tear the picker down.
451 if( event->IsAction( &PCB_ACTIONS::matchPropertiesSettings ) )
452 {
453 if( BOARD_ITEM* source = currentSource() )
454 showSettingsDialog( *source );
455
456 prompt();
457
458 // The enabled set decides what a click would copy, so the mark under the pointer
459 // may mean something different now.
460 updateHover( getViewControls()->GetMousePosition() );
461 continue;
462 }
463
464 event->SetPassEvent();
465 }
466
467 picker->ClearHandlers();
468 hover.ClearBrightening();
469 statusPopup.Hide();
470 canvas()->SetStatusPopup( nullptr );
472
473 if( EDA_ITEM* remaining = board->ResolveItem( sourceId, true ) )
474 m_selectionTool->AddItemToSel( remaining, true );
475
476 frame()->PopTool( aEvent );
477 return 0;
478}
479
480
482{
483 PCB_VIEWERS_SETTINGS_BASE* settings = frame()->GetViewerSettingsBase();
484
485 if( !settings )
486 return;
487
488 std::set<wxString> enabled = settings->m_MatchProperties;
489 MATCH_PROPERTIES_DIALOG dialog( frame(), enabled, MATCH_PROPERTIES_CATALOG::Family( aSource ) );
490
491 if( dialog.ShowModal() == wxID_OK )
492 {
493 settings->m_MatchProperties = std::move( enabled );
494 Pgm().GetSettingsManager().Save( settings );
495 }
496}
497
498
int index
static TOOL_ACTION pickerSubTool
Definition actions.h:250
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
int ShowModal() override
void SetStatusPopup(wxWindow *aPopup)
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
const KIID m_Uuid
Definition eda_item.h:597
The board item under the pointer, and the highlight that follows it.
Represent a selection area (currently a rectangle) in a VIEW, drawn corner-to-corner between two poin...
Definition kiid.h:46
The properties this kind of source can offer.
std::set< wxString > & m_enabled
MATCH_PROPERTIES_DIALOG(wxWindow *aParent, std::set< wxString > &aEnabled, const wxString &aFamily)
wxString keyAt(unsigned int aIndex) const
void onContextMenu(wxContextMenuEvent &aEvent)
void showSettingsDialog(const EDA_ITEM &aSource)
Only the running tool opens this, and only for the source it is copying from.
bool Init() override
Init() is called once upon a registration of the tool.
int Match(const TOOL_EVENT &aEvent)
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
PCB_SELECTION_TOOL * m_selectionTool
bool applyToTargets(const EDA_ITEM &aSource, const std::vector< EDA_ITEM * > &aTargets)
int runInteractive(const TOOL_EVENT &aEvent, const KIID &aSourceId)
The picker session. aSourceId is niluuid when the first click still has to name it.
const std::set< wxString > & enabledKeys()
static TOOL_ACTION matchPropertiesSettings
static TOOL_ACTION matchProperties
Generic tool for picking an item.
void SetAreaPreviewHandler(PCB_SELECTION_TOOL::AREA_PREVIEW aHandler)
Set a handler called with the drag box each time it changes, before anything is selected.
void SetConstructionGeometry(bool aEnable)
Whether the snap system may draw its explanatory geometry over the board this run.
void ClearHandlers()
Handlers only.
The selection tool: currently supports:
T * frame() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
BOARD * board() const
PCB_DRAW_PANEL_GAL * canvas() const
const PCB_SELECTION & selection() const
std::set< wxString > m_MatchProperties
Keyed as "family/Property Name"; see MATCH_PROPERTIES_CATALOG.
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
void SetAreaHandler(AREA_HANDLER aHandler)
Set a handler for a rubber-band drag.
void SetMotionHandler(MOTION_HANDLER aHandler)
Set a handler for mouse motion.
Definition picker_tool.h:92
void SetClickHandler(CLICK_HANDLER aHandler)
Set a handler for mouse click event.
Definition picker_tool.h:81
void SetSnapping(bool aSnap)
Definition picker_tool.h:65
void SetCursor(KICURSOR aCursor)
Definition picker_tool.h:63
void SetCancelHandler(CANCEL_HANDLER aHandler)
Set a handler for cancel events (ESC or context-menu Cancel).
void SetFinalizeHandler(FINALIZE_HANDLER aHandler)
Set a handler for the finalize event.
ITER end()
Definition selection.h:76
ITER begin()
Definition selection.h:75
EDA_ITEM * Front() const
Definition selection.h:176
int Size() const
Returns the number of selected parts.
Definition selection.h:120
wxWindow * GetPanel()
virtual void Popup(wxWindow *aFocus=nullptr)
virtual void Move(const wxPoint &aWhere)
Extension of STATUS_POPUP for displaying a single line text.
void SetText(const wxString &aText)
Display a text.
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:40
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
Generic, UI-independent tool event.
Definition tool_event.h:167
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
@ BULLSEYE
Definition cursors.h:54
#define _(s)
static bool hasMatchableSource(const SELECTION &aSelection)
One item, and one the catalog can read. With several there is no saying which one leads.
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition wxgtk/ui.cpp:839
MATCH_PROPERTIES_RESULT Copy(const EDA_ITEM &aSource, EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
bool Compatible(const EDA_ITEM &aSource, const EDA_ITEM &aTarget)
const std::set< wxString > & AllSafeKeys()
Every property Match Properties may copy, keyed as "family/Property Name".
wxString PropertyLabel(const wxString &aKey)
The translated property name alone, without the family it belongs to.
const std::set< wxString > & DefaultKeys()
The subset enabled until the user says otherwise.
bool AnyEnabledFor(const EDA_ITEM &aItem, const std::set< wxString > &aEnabledKeys)
True if any enabled key names a property of this item's family.
std::vector< EDA_ITEM * > CompatibleTargets(const EDA_ITEM &aSource, const std::vector< EDA_ITEM * > &aCandidates)
wxString FamilyLabel(const wxString &aFamily)
The translated name of a family, for the heading of its group.
std::set< wxString > FamiliesFor(const wxString &aKey)
The families a key reaches. One for a family key, several for a common one.
wxString Family(const EDA_ITEM &aItem)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682