KiCad PCB EDA Suite
Loading...
Searching...
No Matches
properties_panel.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 (C) 2020-2023 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 3
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "properties_panel.h"
23#include <tool/selection.h>
24#include <eda_base_frame.h>
25#include <eda_item.h>
26#include <import_export.h>
27#include <pgm_base.h>
29
30#include <algorithm>
31#include <set>
32
33#include <wx/settings.h>
34#include <wx/stattext.h>
35#include <wx/propgrid/advprops.h>
36
37
38// This is provided by wx >3.3.0
39#if !wxCHECK_VERSION( 3, 3, 0 )
40extern APIIMPORT wxPGGlobalVarsClass* wxPGGlobalVars;
41#endif
42
44 wxPanel( aParent ),
45 m_SuppressGridChangeEvents( 0 ),
46 m_frame( aFrame ),
47 m_splitter_key_proportion( -1 )
48{
49 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
50
51 // on some platforms wxPGGlobalVars is initialized automatically,
52 // but others need an explicit init
53 if( !wxPGGlobalVars )
54 wxPGInitResourceModule();
55
56 // See https://gitlab.com/kicad/code/kicad/-/issues/12297
57 // and https://github.com/wxWidgets/wxWidgets/issues/11787
58 if( wxPGGlobalVars->m_mapEditorClasses.empty() )
59 {
60 wxPGEditor_TextCtrl = nullptr;
61 wxPGEditor_Choice = nullptr;
62 wxPGEditor_ComboBox = nullptr;
63 wxPGEditor_TextCtrlAndButton = nullptr;
64 wxPGEditor_CheckBox = nullptr;
65 wxPGEditor_ChoiceAndButton = nullptr;
66 wxPGEditor_SpinCtrl = nullptr;
67 wxPGEditor_DatePickerCtrl = nullptr;
68 }
69
70 if( !Pgm().m_PropertyGridInitialized )
71 {
72 delete wxPGGlobalVars->m_defaultRenderer;
73 wxPGGlobalVars->m_defaultRenderer = new PG_CELL_RENDERER();
75 }
76
77 m_caption = new wxStaticText( this, wxID_ANY, _( "No objects selected" ) );
78 mainSizer->Add( m_caption, 0, wxALL | wxEXPAND, 5 );
79
80 m_grid = new wxPropertyGrid( this );
81 m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT( "<...>" ) ) );
82 m_grid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
83
84#if wxCHECK_VERSION( 3, 3, 0 )
85 m_grid->SetValidationFailureBehavior( wxPGVFBFlags::MarkCell );
86#else
87 m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
88#endif
89
90#if wxCHECK_VERSION( 3, 3, 0 )
91 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_RETURN );
92 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_NUMPAD_ENTER );
93 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_DOWN );
94 m_grid->AddActionTrigger( wxPGKeyboardAction::PrevProperty, WXK_UP );
95 m_grid->AddActionTrigger( wxPGKeyboardAction::Edit, WXK_SPACE );
96#else
97 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
98 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
99 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
100 m_grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
101 m_grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_SPACE );
102#endif
103
104 m_grid->DedicateKey( WXK_RETURN );
105 m_grid->DedicateKey( WXK_NUMPAD_ENTER );
106 m_grid->DedicateKey( WXK_DOWN );
107 m_grid->DedicateKey( WXK_UP );
108 mainSizer->Add( m_grid, 1, wxEXPAND, 5 );
109
110 m_grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
111
112#ifdef __WXGTK__
113 // Needed for dark mode, on wx 3.0 at least.
114 m_grid->SetCaptionTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
115#endif
116
117 SetFont( KIUI::GetDockedPaneFont( this ) );
118
119 SetSizer( mainSizer );
120 Layout();
121
122 m_grid->CenterSplitter();
123
124 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PROPERTIES_PANEL::onCharHook ), nullptr, this );
125 Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanged ), nullptr, this );
126 Connect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanging ), nullptr, this );
127 Connect( wxEVT_SHOW, wxShowEventHandler( PROPERTIES_PANEL::onShow ), nullptr, this );
128
129 Bind( wxEVT_PG_COL_END_DRAG,
130 [&]( wxPropertyGridEvent& )
131 {
132 m_splitter_key_proportion = static_cast<float>( m_grid->GetSplitterPosition() ) / m_grid->GetSize().x;
133 } );
134
135 Bind( wxEVT_SIZE,
136 [&]( wxSizeEvent& aEvent )
137 {
138 CallAfter( [this]()
139 {
141 } );
142 aEvent.Skip();
143 } );
144
145 m_frame->Bind( EDA_LANG_CHANGED, &PROPERTIES_PANEL::OnLanguageChanged, this );
146}
147
148
150{
151 m_frame->Unbind( EDA_LANG_CHANGED, &PROPERTIES_PANEL::OnLanguageChanged, this );
152}
153
154
155void PROPERTIES_PANEL::OnLanguageChanged( wxCommandEvent& aEvent )
156{
157 if( m_grid->IsEditorFocused() )
158 m_grid->CommitChangesFromEditor();
159
160 m_grid->Clear();
161 m_displayed.clear(); // no ownership of pointers
162
163 UpdateData();
164
165 aEvent.Skip();
166}
167
168
170{
171public:
173 m_panel( aPanel )
174 {
176 }
177
179 {
181 }
182
183private:
185};
186
187
189{
190 SUPPRESS_GRID_CHANGED_EVENTS raii( this );
191
192 auto reset =
193 [&]()
194 {
195 if( m_grid->IsEditorFocused() )
196 m_grid->CommitChangesFromEditor();
197
198 m_grid->Clear();
199 m_displayed.clear();
200 };
201
202 if( aSelection.Empty() )
203 {
204 m_caption->SetLabel( _( "No objects selected" ) );
205 reset();
206 return;
207 }
208 else if( aSelection.Size() == 1 )
209 {
210 m_caption->SetLabel( aSelection.Front()->GetFriendlyName() );
211 }
212 else
213 {
214 m_caption->SetLabel( wxString::Format( _( "%d objects selected" ), aSelection.Size() ) );
215 }
216
217 // Get all the selected types
218 std::set<TYPE_ID> types;
219
220 for( EDA_ITEM* item : aSelection )
221 types.insert( TYPE_HASH( *item ) );
222
223 wxCHECK( !types.empty(), /* void */ ); // already guarded above, but Coverity doesn't know that
224
226 std::set<PROPERTY_BASE*> commonProps;
227 const std::vector<PROPERTY_BASE*>& allProperties = propMgr.GetProperties( *types.begin() );
228
229 copy( allProperties.begin(), allProperties.end(), inserter( commonProps, commonProps.begin() ) );
230
231 std::map<PROPERTY_BASE*, int> displayOrder = propMgr.GetDisplayOrder( *types.begin() );
232 std::vector<wxString> groupDisplayOrder = propMgr.GetGroupDisplayOrder( *types.begin() );
233 std::set<wxString> groups( groupDisplayOrder.begin(), groupDisplayOrder.end() );
234
235 std::set<PROPERTY_BASE*> availableProps;
236
237 // Get all possible properties
238 for( const TYPE_ID& type : types )
239 {
240 const std::vector<PROPERTY_BASE*>& itemProps = propMgr.GetProperties( type );
241 const std::map<PROPERTY_BASE*, int>& itemDisplayOrder = propMgr.GetDisplayOrder( type );
242
243 copy( itemDisplayOrder.begin(), itemDisplayOrder.end(), inserter( displayOrder, displayOrder.begin() ) );
244
245 for( const wxString& group : propMgr.GetGroupDisplayOrder( type ) )
246 {
247 if( !groups.count( group ) )
248 {
249 groupDisplayOrder.emplace_back( group );
250 groups.insert( group );
251 }
252 }
253
254 for( auto it = commonProps.begin(); it != commonProps.end(); /* ++it in the loop */ )
255 {
256 if( !binary_search( itemProps.begin(), itemProps.end(), *it ) )
257 it = commonProps.erase( it );
258 else
259 ++it;
260 }
261 }
262
263 EDA_ITEM* firstItem = aSelection.Front();
264
265 bool isLibraryEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR )
267
268 bool isDesignEditor = m_frame->IsType( FRAME_PCB_EDITOR )
269 || m_frame->IsType( FRAME_SCH );
270
271 // Find a set of properties that is common to all selected items
272 for( PROPERTY_BASE* property : commonProps )
273 {
274 if( property->IsHiddenFromPropertiesManager() )
275 continue;
276
277 if( isLibraryEditor && property->IsHiddenFromLibraryEditors() )
278 continue;
279
280 if( isDesignEditor && property->IsHiddenFromDesignEditors() )
281 continue;
282
283 if( propMgr.IsAvailableFor( TYPE_HASH( *firstItem ), property, firstItem ) )
284 availableProps.insert( property );
285 }
286
287 bool writeable = true;
288 std::set<PROPERTY_BASE*> existingProps;
289
290 for( wxPropertyGridIterator it = m_grid->GetIterator(); !it.AtEnd(); it.Next() )
291 {
292 wxPGProperty* pgProp = it.GetProperty();
293 PROPERTY_BASE* property = propMgr.GetProperty( TYPE_HASH( *firstItem ), pgProp->GetName() );
294
295 // Switching item types? Property may no longer be valid
296 if( !property )
297 continue;
298
299 wxVariant commonVal;
300 wxPGChoices choices;
301
302 extractValueAndWritability( aSelection, property, commonVal, writeable, choices );
303 pgProp->SetValue( commonVal );
304 pgProp->Enable( writeable );
305
306 existingProps.insert( property );
307 }
308
309 if( !existingProps.empty() && existingProps == availableProps )
310 return;
311
312 // Some difference exists: start from scratch
313 reset();
314
315 std::map<wxPGProperty*, int> pgPropOrders;
316 std::map<wxString, std::vector<wxPGProperty*>> pgPropGroups;
317
318 for( PROPERTY_BASE* property : availableProps )
319 {
320 wxPGProperty* pgProp = createPGProperty( property );
321 wxVariant commonVal;
322 wxPGChoices choices;
323
324 if( !extractValueAndWritability( aSelection, property, commonVal, writeable, choices ) )
325 continue;
326
327 if( pgProp )
328 {
329 if( choices.GetCount() )
330 pgProp->SetChoices( choices );
331
332 pgProp->SetValue( commonVal );
333 pgProp->Enable( writeable );
334 m_displayed.push_back( property );
335
336 wxASSERT( displayOrder.count( property ) );
337 pgPropOrders[pgProp] = displayOrder[property];
338 pgPropGroups[property->Group()].emplace_back( pgProp );
339 }
340 }
341
342 const wxString unspecifiedGroupCaption = _( "Basic Properties" );
343
344 for( const wxString& groupName : groupDisplayOrder )
345 {
346 if( !pgPropGroups.count( groupName ) )
347 continue;
348
349 std::vector<wxPGProperty*>& properties = pgPropGroups[groupName];
350 wxString groupCaption = wxGetTranslation( groupName );
351
352 auto groupItem = new wxPropertyCategory( groupName.IsEmpty() ? unspecifiedGroupCaption
353 : groupCaption );
354
355 m_grid->Append( groupItem );
356
357 std::sort( properties.begin(), properties.end(),
358 [&]( wxPGProperty*& aFirst, wxPGProperty*& aSecond )
359 {
360 return pgPropOrders[aFirst] < pgPropOrders[aSecond];
361 } );
362
363 for( wxPGProperty* property : properties )
364 m_grid->Append( property );
365 }
366
368}
369
370
371bool PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
372{
373 const wxAny& any = aItem->Get( aProperty );
374 bool converted = false;
375
376 if( aProperty->HasChoices() )
377 {
378 // handle enums as ints, since there are no default conversion functions for wxAny
379 int tmp;
380 converted = any.GetAs<int>( &tmp );
381
382 if( converted )
383 aValue = wxVariant( tmp );
384 }
385
386 if( !converted ) // all other types
387 converted = any.GetAs( &aValue );
388
389 if( !converted )
390 {
391 wxString propName = aProperty->Name();
392 propName.Replace( ' ', '_' );
393 wxFAIL_MSG( wxString::Format( wxS( "Could not convert wxAny to wxVariant for %s::%s" ),
394 aItem->GetClass(),
395 propName ) );
396 }
397
398 return converted;
399}
400
401
403 wxVariant& aValue, bool& aWritable, wxPGChoices& aChoices )
404{
406 bool different = false;
407 bool first = true;
408
409 aWritable = true;
410
411 for( EDA_ITEM* item : aSelection )
412 {
413 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), aProperty, item ) )
414 return false;
415
416 if( aProperty->IsHiddenFromPropertiesManager() )
417 return false;
418
419 wxPGChoices choices = aProperty->GetChoices( item );
420
421 if( first )
422 {
423 aChoices = choices;
424 first = false;
425 }
426 else
427 {
428 wxArrayString labels = choices.GetLabels();
429 wxArrayInt values = choices.GetValuesForStrings( labels );
430
431 if( labels != aChoices.GetLabels() || values != aChoices.GetValuesForStrings( labels ) )
432 return false;
433 }
434
435 // If read-only for any of the selection, read-only for the whole selection.
436 if( !propMgr.IsWriteableFor( TYPE_HASH( *item ), aProperty, item ) )
437 aWritable = false;
438
439 wxVariant value;
440
441 if( getItemValue( item, aProperty, value ) )
442 {
443 // Null value indicates different property values between items
444 if( !different && !aValue.IsNull() && value != aValue )
445 {
446 different = true;
447 aValue.MakeNull();
448 }
449 else if( !different )
450 {
451 aValue = value;
452 }
453 }
454 else
455 {
456 // getItemValue returned false -- not available for this item
457 return false;
458 }
459 }
460
461 return true;
462}
463
464
465void PROPERTIES_PANEL::onShow( wxShowEvent& aEvent )
466{
467 if( aEvent.IsShown() )
468 UpdateData();
469
470 aEvent.Skip();
471}
472
473
474void PROPERTIES_PANEL::onCharHook( wxKeyEvent& aEvent )
475{
476 // m_grid->IsAnyModified() doesn't work for the first modification
477 if( aEvent.GetKeyCode() == WXK_TAB && !aEvent.ShiftDown() )
478 {
479 wxVariant oldValue;
480
481 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
482 oldValue = prop->GetValue();
483
484 m_grid->CommitChangesFromEditor();
485
486 // If there was no change, treat it as a navigation key
487 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
488 {
489 if( prop->GetValue() == oldValue )
490 aEvent.Skip();
491 }
492
493 return;
494 }
495
496 if( aEvent.GetKeyCode() == WXK_SPACE )
497 {
498 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
499 {
500 if( prop->GetValueType() == wxT( "bool" ) )
501 {
502 m_grid->SetPropertyValue( prop, !prop->GetValue().GetBool() );
503 return;
504 }
505 }
506 }
507
508 if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER
509 || aEvent.GetKeyCode() == WXK_DOWN || aEvent.GetKeyCode() == WXK_UP )
510 {
511 m_grid->CommitChangesFromEditor();
512
513 CallAfter( [this]()
514 {
515 m_grid->SelectProperty( m_grid->GetSelectedProperty(), true );
516 } );
517 }
518
519 aEvent.Skip();
520}
521
522
524{
526 m_grid->CenterSplitter();
527 else
528 m_grid->SetSplitterPosition( m_splitter_key_proportion * m_grid->GetSize().x );
529}
530
531
533{
534 m_splitter_key_proportion = aProportion;
536}
The base frame for deriving all KiCad main window classes.
bool IsType(FRAME_T aType) const
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:98
virtual wxString GetFriendlyName() const
Definition: eda_item.cpp:402
wxAny Get(PROPERTY_BASE *aProperty) const
Definition: inspectable.h:121
virtual wxString GetClass() const =0
Return the class name.
bool m_PropertyGridInitialized
Definition: pgm_base.h:359
Enhanced renderer to work around some limitations in wxWidgets 3.0 capabilities.
float m_splitter_key_proportion
Proportion of the grid column splitter that is used for the key column (0.0 - 1.0)
PROPERTIES_PANEL(wxWindow *aParent, EDA_BASE_FRAME *aFrame)
virtual void valueChanging(wxPropertyGridEvent &aEvent)
wxPropertyGrid * m_grid
bool extractValueAndWritability(const SELECTION &aSelection, PROPERTY_BASE *aProperty, wxVariant &aValue, bool &aWritable, wxPGChoices &aChoices)
Processes a selection and determines whether the given property should be available or not and what t...
wxStaticText * m_caption
void SetSplitterProportion(float aProportion)
std::vector< PROPERTY_BASE * > m_displayed
virtual void OnLanguageChanged(wxCommandEvent &aEvent)
virtual void UpdateData()=0
virtual void valueChanged(wxPropertyGridEvent &aEvent)
bool getItemValue(EDA_ITEM *aItem, PROPERTY_BASE *aProperty, wxVariant &aValue)
Utility to fetch a property value and convert to wxVariant Precondition: aItem is known to have prope...
virtual void rebuildProperties(const SELECTION &aSelection)
Generates the property grid for a given selection of items.
void onCharHook(wxKeyEvent &aEvent)
virtual wxPGProperty * createPGProperty(const PROPERTY_BASE *aProperty) const =0
void onShow(wxShowEvent &aEvent)
EDA_BASE_FRAME * m_frame
bool IsHiddenFromPropertiesManager() const
Definition: property.h:314
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition: property.h:242
wxPGChoices GetChoices(INSPECTABLE *aObject) const
Definition: property.h:264
const wxString & Name() const
Definition: property.h:218
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:74
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
bool IsWriteableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:76
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 ...
const std::map< PROPERTY_BASE *, int > & GetDisplayOrder(TYPE_ID aType) const
const std::vector< wxString > & GetGroupDisplayOrder(TYPE_ID aType) const
EDA_ITEM * Front() const
Definition: selection.h:177
int Size() const
Returns the number of selected parts.
Definition: selection.h:121
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:115
SUPPRESS_GRID_CHANGED_EVENTS(PROPERTIES_PANEL *aPanel)
#define _(s)
Base window classes and related definitions.
@ FRAME_PCB_EDITOR
Definition: frame_type.h:42
@ FRAME_SCH_SYMBOL_EDITOR
Definition: frame_type.h:35
@ FRAME_SCH
Definition: frame_type.h:34
@ FRAME_FOOTPRINT_EDITOR
Definition: frame_type.h:43
#define APIIMPORT
Definition: import_export.h:43
KICOMMON_API wxFont GetDockedPaneFont(wxWindow *aWindow)
Definition: ui_common.cpp:144
PGM_BASE & Pgm()
The global program "get" accessor.
Definition: pgm_base.cpp:902
see class PGM_BASE
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
#define TYPE_HASH(x)
Definition: property.h:72
size_t TYPE_ID
Unique type identifier.
Definition: property_mgr.h:47