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